Mp 2540 farm overview (#172)

* tidy: refactor text

* tidy: refactor text

* fix display of vaults table

* feat: added unstyled select

* tidy: useToggle

* tidy: useToggle

* add vaults to types

* MP-2344: first unstyled version of Select

* fix: fixed the build

* MP-2344: progress on the Select

* MP-2344: almost finished the Select

* implement basic vault modal (no logic)

* 🍱  max + displaycur for token input

* Convert to BN for TokenInputs

* env: update wallet-connector

* fix: fixed build errors and relative imports

* fix: updated TokenInput

* tidy: format

* fix: BN instead of new BigNumber

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
This commit is contained in:
Bob van der Helm 2023-05-02 15:55:32 +08:00 committed by GitHub
parent ac09862f1f
commit cd5ec3ee3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 922 additions and 358 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "mars-v2-frontend", "name": "mars-v2-frontend",
"version": "0.1.0", "version": "2.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "next build", "build": "next build",
@ -13,13 +13,13 @@
"dependencies": { "dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.30.1", "@cosmjs/cosmwasm-stargate": "^0.30.1",
"@cosmjs/stargate": "^0.30.1", "@cosmjs/stargate": "^0.30.1",
"@marsprotocol/wallet-connector": "^1.5.3", "@marsprotocol/wallet-connector": "^1.5.4",
"@sentry/nextjs": "^7.47.0", "@sentry/nextjs": "^7.48.0",
"@tanstack/react-table": "^8.8.5", "@tanstack/react-table": "^8.8.5",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"bignumber.js": "^9.1.1", "bignumber.js": "^9.1.1",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"graphql-request": "^5.2.0", "graphql-request": "^6.0.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"next": "^13.3.0", "next": "^13.3.0",
"react": "^18.2.0", "react": "^18.2.0",
@ -30,14 +30,14 @@
"react-toastify": "^9.1.2", "react-toastify": "^9.1.2",
"react-use-clipboard": "^1.0.9", "react-use-clipboard": "^1.0.9",
"recharts": "^2.5.0", "recharts": "^2.5.0",
"swr": "^2.1.2", "swr": "^2.1.3",
"tailwind-scrollbar-hide": "^1.1.7", "tailwind-scrollbar-hide": "^1.1.7",
"zustand": "^4.3.7" "zustand": "^4.3.7"
}, },
"devDependencies": { "devDependencies": {
"@svgr/webpack": "^7.0.0", "@svgr/webpack": "^7.0.0",
"@types/node": "^18.15.11", "@types/node": "^18.15.11",
"@types/react": "18.0.33", "@types/react": "18.0.35",
"@types/react-dom": "18.0.11", "@types/react-dom": "18.0.11",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"eslint": "8.38.0", "eslint": "8.38.0",
@ -45,7 +45,7 @@
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"prettier": "^2.8.7", "prettier": "^2.8.7",
"prettier-plugin-tailwindcss": "^0.2.6", "prettier-plugin-tailwindcss": "^0.2.7",
"tailwindcss": "^3.3.1", "tailwindcss": "^3.3.1",
"typescript": "5.0.4" "typescript": "5.0.4"
}, },

View File

@ -4,6 +4,7 @@ import { headers } from 'next/headers'
import AccountDetails from 'components/Account/AccountDetails' import AccountDetails from 'components/Account/AccountDetails'
import Background from 'components/Background' import Background from 'components/Background'
import FetchPrices from 'components/FetchPrices' import FetchPrices from 'components/FetchPrices'
import Footer from 'components/Footer'
import DesktopHeader from 'components/Header/DesktopHeader' import DesktopHeader from 'components/Header/DesktopHeader'
import { Modals } from 'components/Modals' import { Modals } from 'components/Modals'
import Toaster from 'components/Toaster' import Toaster from 'components/Toaster'
@ -23,13 +24,14 @@ export default function RootLayout(props: { children: React.ReactNode }) {
<FetchPrices /> <FetchPrices />
<main <main
className={classNames( className={classNames(
'relative flex justify-center py-6', 'relative flex justify-center pt-6',
'lg:mt-[65px] lg:h-[calc(100vh-65px)]', 'lg:mt-[65px] lg:h-[calc(100vh-89px)]',
)} )}
> >
<div className='flex max-w-content flex-grow flex-col flex-wrap'>{props.children}</div> <div className='flex max-w-content flex-grow flex-col flex-wrap'>{props.children}</div>
<AccountDetails /> <AccountDetails />
</main> </main>
<Footer />
<Modals /> <Modals />
<Toaster /> <Toaster />
</body> </body>

View File

@ -1,6 +1,6 @@
import Card from 'components/Card' import Card from 'components/Card'
import { Plus, Subtract } from 'components/Icons' import { Plus, Subtract } from 'components/Icons'
import { Text } from 'components/Text' import Text from 'components/Text'
interface Props { interface Props {
items: Item[] items: Item[]

View File

@ -1,7 +1,7 @@
'use client' 'use client'
import { Gauge } from 'components/Gauge' import { Gauge } from 'components/Gauge'
import { Heart } from 'components/Icons' import { Heart } from 'components/Icons'
import { Text } from 'components/Text' import Text from 'components/Text'
import { isNumber } from 'utils/parsers' import { isNumber } from 'utils/parsers'
import useParams from 'utils/route' import useParams from 'utils/route'

View File

@ -1,8 +1,8 @@
'use client' 'use client'
import classNames from 'classnames' import classNames from 'classnames'
import { usePathname, useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react' import { useEffect } from 'react'
import AccountStats from 'components/Account/AccountStats' import AccountStats from 'components/Account/AccountStats'
import { Button } from 'components/Button' import { Button } from 'components/Button'
@ -10,13 +10,12 @@ import Card from 'components/Card'
import { ArrowCircledTopRight, ArrowDownLine, ArrowUpLine, TrashBin } from 'components/Icons' import { ArrowCircledTopRight, ArrowDownLine, ArrowUpLine, TrashBin } from 'components/Icons'
import Radio from 'components/Radio' import Radio from 'components/Radio'
import SwitchWithLabel from 'components/SwitchWithLabel' import SwitchWithLabel from 'components/SwitchWithLabel'
import { Text } from 'components/Text' import Text from 'components/Text'
import { ASSETS } from 'constants/assets' import useToggle from 'hooks/useToggle'
import useParams, { getRoute } from 'utils/route'
import useStore from 'store' import useStore from 'store'
import { calculateAccountBalance } from 'utils/accounts' import { calculateAccountBalance } from 'utils/accounts'
import { hardcodedFee } from 'utils/contants' import { hardcodedFee } from 'utils/contants'
import { formatValue } from 'utils/formatters' import useParams, { getRoute } from 'utils/route'
interface Props { interface Props {
setShowFundAccount: (showFundAccount: boolean) => void setShowFundAccount: (showFundAccount: boolean) => void
@ -37,7 +36,7 @@ export default function AccountList(props: Props) {
const deleteAccount = useStore((s) => s.deleteAccount) const deleteAccount = useStore((s) => s.deleteAccount)
const [isLending, setIsLending] = useState(false) const [isLending, setIsLending] = useToggle()
const accountSelected = !!selectedAccount && !isNaN(Number(selectedAccount)) const accountSelected = !!selectedAccount && !isNaN(Number(selectedAccount))
const selectedAccountDetails = props.accounts.find((account) => account.id === selectedAccount) const selectedAccountDetails = props.accounts.find((account) => account.id === selectedAccount)
const selectedAccountBalance = selectedAccountDetails const selectedAccountBalance = selectedAccountDetails
@ -52,8 +51,8 @@ export default function AccountList(props: Props) {
} }
} }
function onChangeLendSwitch(isLending: boolean) { function onChangeLendSwitch() {
setIsLending(isLending) setIsLending(!isLending)
/* TODO: handle lending assets */ /* TODO: handle lending assets */
} }

View File

@ -2,7 +2,7 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react' import { useState } from 'react'
import AccountList from 'components/Account/AccountList' import AccountList from 'components/Account/AccountList'
import CreateAccount from 'components/Account/CreateAccount' import CreateAccount from 'components/Account/CreateAccount'
@ -10,15 +10,15 @@ import FundAccount from 'components/Account/FundAccount'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import { Account, Plus, PlusCircled } from 'components/Icons' import { Account, Plus, PlusCircled } from 'components/Icons'
import { Overlay } from 'components/Overlay/Overlay' import Overlay from 'components/Overlay/Overlay'
import { Text } from 'components/Text' import Text from 'components/Text'
import useParams from 'utils/route' import useToggle from 'hooks/useToggle'
import useStore from 'store' import useStore from 'store'
import { hardcodedFee } from 'utils/contants' import { hardcodedFee } from 'utils/contants'
import { isNumber } from 'utils/parsers' import { isNumber } from 'utils/parsers'
import useParams from 'utils/route'
const menuClasses = const menuClasses = 'absolute isolate flex w-full flex-wrap scrollbar-hide'
'absolute isolate flex w-full flex-wrap overflow-y-scroll scrollbar-hide scroll-smooth'
interface Props { interface Props {
accounts: Account[] accounts: Account[]
@ -28,8 +28,8 @@ export default function AccountMenuContent(props: Props) {
const router = useRouter() const router = useRouter()
const params = useParams() const params = useParams()
const createAccount = useStore((s) => s.createAccount) const createAccount = useStore((s) => s.createAccount)
const [showMenu, setShowMenu] = useState(false) const [showMenu, setShowMenu] = useToggle()
const [isCreating, setIsCreating] = useState(false) const [isCreating, setIsCreating] = useToggle()
const selectedAccountId = params.accountId const selectedAccountId = params.accountId
const hasCreditAccounts = !!props.accounts.length const hasCreditAccounts = !!props.accounts.length
@ -72,7 +72,10 @@ export default function AccountMenuContent(props: Props) {
: 'Create Account'} : 'Create Account'}
</Button> </Button>
<Overlay <Overlay
className='max-w-screen right-0 mt-2 flex h-[530px] w-[336px] overflow-hidden' className={classNames(
'max-w-screen right-0 mt-2 flex h-[530px] w-[336px]',
!showFundAccount && 'overflow-hidden',
)}
show={showMenu} show={showMenu}
setShow={setShowMenu} setShow={setShowMenu}
> >
@ -96,7 +99,13 @@ export default function AccountMenuContent(props: Props) {
onClick={createAccountHandler} onClick={createAccountHandler}
/> />
</div> </div>
<div className={classNames(menuClasses, 'top-[54px] h-[calc(100%-54px)] items-start')}> <div
className={classNames(
menuClasses,
!showFundAccount && 'overflow-y-scroll scroll-smooth',
'top-[54px] h-[calc(100%-54px)] items-start',
)}
>
{isAccountSelected && isLoadingAccount && ( {isAccountSelected && isLoadingAccount && (
<div className='flex h-full w-full items-center justify-center p-4'> <div className='flex h-full w-full items-center justify-center p-4'>
<CircularProgress size={40} /> <CircularProgress size={40} />
@ -108,7 +117,13 @@ export default function AccountMenuContent(props: Props) {
</div> </div>
</> </>
) : ( ) : (
<div className={classNames(menuClasses, 'inset-0 h-full items-end bg-account')}> <div
className={classNames(
menuClasses,
!showFundAccount && 'overflow-y-scroll scroll-smooth',
'inset-0 h-full items-end bg-account',
)}
>
{showCreateAccount ? ( {showCreateAccount ? (
<CreateAccount createAccount={createAccountHandler} isCreating={isCreating} /> <CreateAccount createAccount={createAccountHandler} isCreating={isCreating} />
) : showFundAccount ? ( ) : showFundAccount ? (

View File

@ -1,7 +1,7 @@
'use client' 'use client'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import { Heart, Shield } from 'components/Icons' import { Heart, Shield } from 'components/Icons'
import { Text } from 'components/Text' import Text from 'components/Text'
import useStore from 'store' import useStore from 'store'
interface Props { interface Props {

View File

@ -1,7 +1,7 @@
import Accordion from 'components/Accordion' import Accordion from 'components/Accordion'
import Card from 'components/Card' import Card from 'components/Card'
import { ArrowChartLineUp, Shield } from 'components/Icons' import { ArrowChartLineUp, Shield } from 'components/Icons'
import { Text } from 'components/Text' import Text from 'components/Text'
import useParams from 'utils/route' import useParams from 'utils/route'
export default function AccountSummary() { export default function AccountSummary() {

View File

@ -2,7 +2,7 @@
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/Icons'
import { Text } from 'components/Text' import Text from 'components/Text'
interface Props { interface Props {
isCreating: boolean isCreating: boolean

View File

@ -1,17 +1,19 @@
'use client' 'use client'
import BigNumber from 'bignumber.js'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import BigNumber from 'bignumber.js'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { ArrowRight, Cross } from 'components/Icons' import { ArrowRight, Cross } from 'components/Icons'
import SwitchWithLabel from 'components/SwitchWithLabel' import SwitchWithLabel from 'components/SwitchWithLabel'
import { Text } from 'components/Text' import Text from 'components/Text'
import TokenInputWithSlider from 'components/TokenInputWithSlider' import TokenInputWithSlider from 'components/TokenInputWithSlider'
import { ASSETS } from 'constants/assets' import { ASSETS } from 'constants/assets'
import useParams from 'utils/route' import useToggle from 'hooks/useToggle'
import useStore from 'store' import useStore from 'store'
import { hardcodedFee } from 'utils/contants' import { hardcodedFee } from 'utils/contants'
import { BN } from 'utils/helpers'
import useParams from 'utils/route'
interface Props { interface Props {
setShowFundAccount: (show: boolean) => void setShowFundAccount: (show: boolean) => void
@ -22,27 +24,34 @@ export default function FundAccount(props: Props) {
const params = useParams() const params = useParams()
const deposit = useStore((s) => s.deposit) const deposit = useStore((s) => s.deposit)
const [amount, setAmount] = useState(0) const [amount, setAmount] = useState(BN(0))
const [isLending, setIsLending] = useState(false) const [asset, setAsset] = useState<Asset>(ASSETS[0])
const [isFunding, setIsFunding] = useState(false) const [isLending, setIsLending] = useToggle()
const [isFunding, setIsFunding] = useToggle()
const onChangeAmount = useCallback((amount: number) => { const onChangeAmount = useCallback((amount: BigNumber) => {
setAmount(amount) setAmount(amount)
}, []) }, [])
const handleLendAssets = useCallback((val: boolean) => { const onChangeAsset = useCallback((asset: Asset) => {
setIsLending(val) setAsset(asset)
/* TODO: handle lending assets */
}, []) }, [])
const handleLendAssets = useCallback(
(val: boolean) => {
setIsLending(val)
/* TODO: handle lending assets */
},
[setIsLending],
)
async function onDeposit() { async function onDeposit() {
setIsFunding(true) setIsFunding(true)
// TODO: Make this dynamic (token select)
const result = await deposit({ const result = await deposit({
fee: hardcodedFee, fee: hardcodedFee,
accountId: params.accountId, accountId: params.accountId,
coin: { coin: {
denom: ASSETS[0].denom, denom: asset.denom,
amount: amount.toString(), amount: amount.toString(),
}, },
}) })
@ -73,27 +82,28 @@ export default function FundAccount(props: Props) {
your Osmosis address has no assets. your Osmosis address has no assets.
</Text> </Text>
<TokenInputWithSlider <TokenInputWithSlider
asset={ASSETS[0]}
onChange={onChangeAmount} onChange={onChangeAmount}
onChangeAsset={onChangeAsset}
amount={amount} amount={amount}
max={new BigNumber(1).shiftedBy(ASSETS[0].decimals).toNumber()} max={BN(1).shiftedBy(ASSETS[0].decimals)}
className='mb-4' className='mb-4'
disabled={isFunding} disabled={isFunding}
hasSelect
/> />
<div className='mb-4 w-full border-b border-white/10' /> <div className='mb-4 w-full border-b border-white/10' />
<SwitchWithLabel <SwitchWithLabel
name='isLending' name='isLending'
label='Lend assets to earn yield' label='Lend assets to earn yield'
value={isLending} value={isLending}
onChange={handleLendAssets} onChange={() => handleLendAssets(!isLending)}
className='mb-4' className='mb-4'
tooltip="Fund your account and lend assets effortlessly! By lending, you'll earn attractive interest (APY) without impacting your LTV. It's a win-win situation - don't miss out on this easy opportunity to grow your holdings!" tooltip="Fund your account and lend assets effortlessly! By lending, you'll earn attractive interest (APY) without impacting your LTV. It's a win-win situation - don't miss out on this easy opportunity to grow your holdings!"
disabled={isFunding || amount === 0} disabled={isFunding || amount.isEqualTo(0)}
/> />
<Button <Button
className='w-full' className='w-full'
showProgressIndicator={isFunding} showProgressIndicator={isFunding}
disabled={amount === 0} disabled={amount.isEqualTo(0)}
text='Fund Account' text='Fund Account'
rightIcon={<ArrowRight />} rightIcon={<ArrowRight />}
onClick={onDeposit} onClick={onDeposit}

View File

@ -9,10 +9,10 @@ import {
YAxis, YAxis,
} from 'recharts' } from 'recharts'
import { formatValue } from 'utils/formatters'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { Text } from 'components/Text' import Text from 'components/Text'
import useStore from 'store' import useStore from 'store'
import { formatValue } from 'utils/formatters'
export const RiskChart = ({ data }: RiskChartProps) => { export const RiskChart = ({ data }: RiskChartProps) => {
const enableAnimations = useStore((s) => s.enableAnimations) const enableAnimations = useStore((s) => s.enableAnimations)

View File

@ -17,7 +17,7 @@ import AssetExpanded from 'components/Borrow/AssetExpanded'
import { AssetRow } from 'components/Borrow/AssetRow' import { AssetRow } from 'components/Borrow/AssetRow'
import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons' import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import { Text } from 'components/Text' import Text from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell' import TitleAndSubCell from 'components/TitleAndSubCell'
import { getMarketAssets } from 'utils/assets' import { getMarketAssets } from 'utils/assets'
import { formatPercent } from 'utils/formatters' import { formatPercent } from 'utils/formatters'

View File

@ -2,7 +2,7 @@ import classNames from 'classnames'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { Text } from 'components/Text' import Text from 'components/Text'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import useStore from 'store' import useStore from 'store'
@ -65,7 +65,7 @@ export const BorrowCapacity = ({
className={classNames( className={classNames(
enableAnimations && 'duration-800 transition-[opcity] delay-[1600ms]', enableAnimations && 'duration-800 transition-[opcity] delay-[1600ms]',
'text-3xs-caps', 'text-3xs-caps',
limitPercentOfMax ? 'opacity-60' : 'opacity-0', limitPercentOfMax ? 'opacity-50' : 'opacity-0',
)} )}
> >
<FormattedNumber animate amount={limit} /> <FormattedNumber animate amount={limit} />
@ -137,7 +137,7 @@ export const BorrowCapacity = ({
</div> </div>
</Tooltip> </Tooltip>
{!hideValues && ( {!hideValues && (
<div className='mt-2 flex opacity-60 text-3xs-caps'> <div className='mt-2 flex opacity-50 text-3xs-caps'>
<FormattedNumber animate amount={balance} className='mr-1' /> <FormattedNumber animate amount={balance} className='mr-1' />
<span className='mr-1'>of</span> <span className='mr-1'>of</span>
<FormattedNumber animate amount={max} /> <FormattedNumber animate amount={max} />

View File

@ -1,5 +1,6 @@
import Image from 'next/image' import Image from 'next/image'
import { useState } from 'react' import { useState } from 'react'
import BigNumber from 'bignumber.js'
import AccountSummary from 'components/Account/AccountSummary' import AccountSummary from 'components/Account/AccountSummary'
import { Button } from 'components/Button' import { Button } from 'components/Button'
@ -7,18 +8,19 @@ import Card from 'components/Card'
import Divider from 'components/Divider' import Divider from 'components/Divider'
import { ArrowRight } from 'components/Icons' import { ArrowRight } from 'components/Icons'
import { Modal } from 'components/Modal' import { Modal } from 'components/Modal'
import { Text } from 'components/Text' import Text from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell' import TitleAndSubCell from 'components/TitleAndSubCell'
import TokenInputWithSlider from 'components/TokenInputWithSlider'
import useStore from 'store' import useStore from 'store'
import { hardcodedFee } from 'utils/contants' import { hardcodedFee } from 'utils/contants'
import { formatPercent, formatValue } from 'utils/formatters' import { formatPercent, formatValue } from 'utils/formatters'
import TokenInputWithSlider from 'components/TokenInputWithSlider'
import useParams from 'utils/route' import useParams from 'utils/route'
import { BN } from 'utils/helpers'
export default function BorrowModal() { export default function BorrowModal() {
const params = useParams() const params = useParams()
const [percentage, setPercentage] = useState(0) const [percentage, setPercentage] = useState(0)
const [amount, setAmount] = useState(0) const [amount, setAmount] = useState(BN(0))
const [selectedAccount, setSelectedAccount] = useState(params.accountId) const [selectedAccount, setSelectedAccount] = useState(params.accountId)
const modal = useStore((s) => s.borrowModal) const modal = useStore((s) => s.borrowModal)
const borrow = useStore((s) => s.borrow) const borrow = useStore((s) => s.borrow)
@ -33,7 +35,7 @@ export default function BorrowModal() {
function setOpen(isOpen: boolean) { function setOpen(isOpen: boolean) {
useStore.setState({ borrowModal: null }) useStore.setState({ borrowModal: null })
setAmount(0) setAmount(BN(0))
setPercentage(0) setPercentage(0)
} }
@ -75,7 +77,7 @@ export default function BorrowModal() {
if ((modal.marketData as BorrowAssetActive)?.debt) if ((modal.marketData as BorrowAssetActive)?.debt)
debtAmount = Number((modal.marketData as BorrowAssetActive).debt) debtAmount = Number((modal.marketData as BorrowAssetActive).debt)
const maxAmount = modal.isRepay ? debtAmount : liquidityAmount const max = BN(modal.isRepay ? debtAmount : liquidityAmount)
return ( return (
<Modal <Modal
@ -115,9 +117,12 @@ export default function BorrowModal() {
> >
<TokenInputWithSlider <TokenInputWithSlider
asset={modal.asset} asset={modal.asset}
onChange={setAmount} onChange={(val) => {
console.log('new value received', val)
setAmount(val)
}}
amount={amount} amount={amount}
max={maxAmount} max={max}
/> />
<Divider /> <Divider />
<Text size='lg'>{modal.isRepay ? 'Repay for' : 'Borrow to'}</Text> <Text size='lg'>{modal.isRepay ? 'Repay for' : 'Borrow to'}</Text>

View File

@ -1,7 +1,7 @@
import classNames from 'classnames' import classNames from 'classnames'
import { ReactElement, ReactNode } from 'react' import { ReactElement, ReactNode } from 'react'
import { Text } from 'components/Text' import Text from 'components/Text'
interface Props { interface Props {
children: ReactNode children: ReactNode

View File

@ -1,6 +1,6 @@
import classNames from 'classnames' import classNames from 'classnames'
import { Text } from 'components/Text' import Text from 'components/Text'
import useStore from 'store' import useStore from 'store'
interface Props { interface Props {

View File

@ -2,7 +2,7 @@ import { Suspense } from 'react'
import Card from 'components/Card' import Card from 'components/Card'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import { Text } from 'components/Text' import Text from 'components/Text'
async function Content(props: PageProps) { async function Content(props: PageProps) {
const address = props.params.address const address = props.params.address

View File

@ -3,14 +3,12 @@ import BigNumber from 'bignumber.js'
import useStore from 'store' import useStore from 'store'
import { getMarketAssets } from 'utils/assets' import { getMarketAssets } from 'utils/assets'
import { BN } from 'utils/helpers'
import { FormattedNumber } from './FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
interface Props { interface Props {
coin: Coin coin: Coin
className?: string className?: string
prefixClassName?: string
valueClassName?: string
isApproximation?: boolean isApproximation?: boolean
} }
@ -26,7 +24,7 @@ export default function DisplayCurrency(props: Props) {
if (!price || !asset || !displayPrice) return '0' if (!price || !asset || !displayPrice) return '0'
return new BigNumber(coin.amount) return BN(coin.amount)
.times(price.amount) .times(price.amount)
.div(displayPrice.amount) .div(displayPrice.amount)
.integerValue(BigNumber.ROUND_HALF_DOWN) .integerValue(BigNumber.ROUND_HALF_DOWN)
@ -35,13 +33,14 @@ export default function DisplayCurrency(props: Props) {
return ( return (
<FormattedNumber <FormattedNumber
className={props.className}
amount={convertToDisplayAmount(props.coin)} amount={convertToDisplayAmount(props.coin)}
options={{ options={{
minDecimals: 0, minDecimals: 0,
maxDecimals: 2, maxDecimals: 2,
abbreviated: true, abbreviated: true,
decimals: displayCurrency.decimals, decimals: displayCurrency.decimals,
prefix: `${props.isApproximation ? '~' : ''}${ prefix: `${props.isApproximation ? '~ ' : ''}${
displayCurrency.prefix ? displayCurrency.prefix : '' displayCurrency.prefix ? displayCurrency.prefix : ''
}`, }`,
suffix: displayCurrency.symbol ? ` ${displayCurrency.symbol}` : '', suffix: displayCurrency.symbol ? ` ${displayCurrency.symbol}` : '',

View File

@ -1,11 +1,10 @@
import { Suspense } from 'react' import { Suspense } from 'react'
import Card from 'components/Card' import Card from 'components/Card'
import { getVaults } from 'utils/api' import { VaultTable } from 'components/Earn/vault/VaultTable'
import { Text } from 'components/Text' import Text from 'components/Text'
import { VAULTS } from 'constants/vaults' import { VAULTS } from 'constants/vaults'
import { getVaults } from 'utils/api'
import { VaultTable } from './VaultTable'
async function Content() { async function Content() {
const vaults = await getVaults() const vaults = await getVaults()

View File

@ -1,10 +1,8 @@
import { Suspense } from 'react' import { Suspense } from 'react'
import Card from 'components/Card' import Card from 'components/Card'
import VaultCard from 'components/Earn/vault/VaultCard'
import { getVaults } from 'utils/api' import { getVaults } from 'utils/api'
import { Text } from 'components/Text'
import VaultCard from './VaultCard'
async function Content() { async function Content() {
const vaults = await getVaults() const vaults = await getVaults()
@ -20,7 +18,13 @@ async function Content() {
contentClassName='grid grid-cols-3' contentClassName='grid grid-cols-3'
> >
{featuredVaults.map((vault) => ( {featuredVaults.map((vault) => (
<VaultCard key={vault.address} vault={vault} /> <VaultCard
key={vault.address}
vault={vault}
title={vault.name}
subtitle='Hot off the presses'
provider={vault.provider}
/>
))} ))}
</Card> </Card>
) )

View File

@ -1,33 +1,40 @@
'use client' 'use client'
import Image from 'next/image'
import { Text } from 'components/Text'
import { getAssetByDenom } from 'utils/assets'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { formatPercent, formatValue } from 'utils/formatters'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import VaultLogo from 'components/Earn/VaultLogo' import VaultLogo from 'components/Earn/vault/VaultLogo'
import Text from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell'
import useStore from 'store'
import { getAssetByDenom } from 'utils/assets'
import { formatPercent, formatValue } from 'utils/formatters'
interface Props { interface Props {
vault: Vault vault: Vault
title: string
subtitle: string
provider?: string
unbondingPeriod?: number
} }
export default function VaultCard(props: Props) { export default function VaultCard(props: Props) {
function openVaultModal() {} function openVaultModal() {
useStore.setState({ vaultModal: { vault: props.vault } })
}
return ( return (
<div className='border-r-[1px] border-r-white/10 p-4'> <div className='border-r-[1px] border-r-white/10 p-4'>
<div className='align-center mb-8 flex justify-between'> <div className='align-center mb-8 flex justify-between'>
<div> <div>
<Text size='xs' className='mb-2 text-white/60'> <Text size='xs' className='mb-2 text-white/60'>
Hot off the presses {props.subtitle}
</Text> </Text>
<span className='flex'> <span className='flex'>
<Text className='mr-2 font-bold'>{props.vault.name}</Text> <Text className='mr-2 font-bold'>{props.title}</Text>
<Text size='sm' className='text-white/60'> {props.provider && (
via {props.vault.provider} <Text size='sm' className='text-white/60'>
</Text> via {props.provider}
</Text>
)}
</span> </span>
</div> </div>
<VaultLogo vault={props.vault} /> <VaultLogo vault={props.vault} />

View File

@ -0,0 +1,53 @@
import { Row } from '@tanstack/react-table'
import VaultCard from 'components/Earn/vault/VaultCard'
import Text from 'components/Text'
import useStore from 'store'
interface Props {
row: Row<Vault>
resetExpanded: (defaultState?: boolean | undefined) => void
}
export default function VaultExpanded(props: Props) {
function enterVaultHandler() {
useStore.setState({ vaultModal: { vault: props.row.original } })
}
return (
<tr
key={props.row.id}
className='cursor-pointer bg-black/20 transition-colors'
onClick={(e) => {
e.preventDefault()
const isExpanded = props.row.getIsExpanded()
props.resetExpanded()
!isExpanded && props.row.toggleExpanded()
}}
>
<td colSpan={5}>
<Text className='border-b border-white/10 px-4 py-5 '>Select bonding period</Text>
<div className='grid grid-cols-3 md:[&>div:nth-child(3)]:border-none'>
<VaultCard
vault={props.row.original}
title='1 day unbonding'
subtitle='$0 deposited'
unbondingPeriod={1}
/>
<VaultCard
vault={props.row.original}
title='7 day unbonding'
subtitle='$0 deposited'
unbondingPeriod={7}
/>
<VaultCard
vault={props.row.original}
title='14 day unbonding'
subtitle='$0 deposited'
unbondingPeriod={14}
/>
</div>
</td>
</tr>
)
}

View File

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

View File

@ -11,8 +11,7 @@ export const VaultRow = (props: AssetRowProps) => {
<tr <tr
key={props.row.id} key={props.row.id}
className={classNames( className={classNames(
'cursor-pointer transition-colors', 'bg-white/3 cursor-pointer border-b border-t border-white/5 transition-colors hover:bg-white/5',
props.row.getIsExpanded() ? ' bg-black/20' : 'bg-white/0 hover:bg-white/5',
)} )}
onClick={(e) => { onClick={(e) => {
e.preventDefault() e.preventDefault()

View File

@ -11,15 +11,15 @@ import {
import classNames from 'classnames' import classNames from 'classnames'
import React from 'react' import React from 'react'
import VaultExpanded from 'components/Earn/vault/VaultExpanded'
import VaultLogo from 'components/Earn/vault/VaultLogo'
import { VaultRow } from 'components/Earn/vault/VaultRow'
import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons' import { ChevronDown, SortAsc, SortDesc, SortNone } from 'components/Icons'
import { Text } from 'components/Text' import Text from 'components/Text'
import { getAssetByDenom, getMarketAssets } from 'utils/assets'
import VaultLogo from 'components/Earn/VaultLogo'
import TitleAndSubCell from 'components/TitleAndSubCell' import TitleAndSubCell from 'components/TitleAndSubCell'
import { convertPercentage, formatPercent, formatValue } from 'utils/formatters'
import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults' import { VAULT_DEPOSIT_BUFFER } from 'constants/vaults'
import { getAssetByDenom } from 'utils/assets'
import { VaultRow } from 'components/Earn/VaultRow' import { convertPercentage, formatPercent, formatValue } from 'utils/formatters'
type Props = { type Props = {
data: Vault[] data: Vault[]
@ -27,7 +27,6 @@ type Props = {
export const VaultTable = (props: Props) => { export const VaultTable = (props: Props) => {
const [sorting, setSorting] = React.useState<SortingState>([]) const [sorting, setSorting] = React.useState<SortingState>([])
const marketAssets = getMarketAssets()
const columns = React.useMemo<ColumnDef<Vault>[]>( const columns = React.useMemo<ColumnDef<Vault>[]>(
() => [ () => [
@ -90,7 +89,7 @@ export const VaultTable = (props: Props) => {
), ),
}, },
], ],
[marketAssets, props.data], [],
) )
const table = useReactTable({ const table = useReactTable({
@ -157,6 +156,7 @@ export const VaultTable = (props: Props) => {
return ( return (
<React.Fragment key={`${row.id}_subrow`}> <React.Fragment key={`${row.id}_subrow`}>
<VaultRow row={row} resetExpanded={table.resetExpanded} /> <VaultRow row={row} resetExpanded={table.resetExpanded} />
<VaultExpanded row={row} resetExpanded={table.resetExpanded} />
</React.Fragment> </React.Fragment>
) )
} }

15
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,15 @@
import Text from 'components/Text'
import packageInfo from '../../package.json'
export default function Footer() {
return (
<footer className='flex h-6 w-full items-center justify-center'>
<div className='w-full max-w-screen-lg text-right'>
<Text size='xs' className='opacity-50'>
v{packageInfo.version}
</Text>
</div>
</footer>
)
}

View File

@ -1,7 +1,7 @@
import classNames from 'classnames' import classNames from 'classnames'
import { Text } from 'components/Text'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import Text from 'components/Text'
interface ValueData extends FormattedNumberProps { interface ValueData extends FormattedNumberProps {
format?: 'number' | 'string' format?: 'number' | 'string'

View File

@ -1,9 +1,11 @@
'use client' 'use client'
import BorrowModal from 'components/BorrowModal' import BorrowModal from 'components/BorrowModal'
import VaultModal from 'components/Earn/vault/VaultModal'
export const Modals = () => ( export const Modals = () => (
<> <>
<BorrowModal /> <BorrowModal />
<VaultModal />
</> </>
) )

View File

@ -4,47 +4,43 @@ import BigNumber from 'bignumber.js'
import classNames from 'classnames' import classNames from 'classnames'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { formatValue } from 'utils/formatters'
import { BN } from 'utils/helpers'
interface Props { interface Props {
asset: Asset asset: Asset
amount: number amount: BigNumber
min?: BigNumber
max?: BigNumber
className: string className: string
maxDecimals: number maxDecimals: number
minValue?: number
max?: number
maxLength?: number maxLength?: number
allowNegative?: boolean allowNegative?: boolean
style?: {} style?: {}
disabled?: boolean disabled?: boolean
onChange: (amount: BigNumber) => void
onChange: (amount: number) => void
onBlur?: () => void onBlur?: () => void
onFocus?: () => void onFocus?: () => void
onRef?: (ref: React.RefObject<HTMLInputElement>) => void onRef?: (ref: React.RefObject<HTMLInputElement>) => void
} }
function magnify(value: number, asset: Asset) {
return value === 0 ? 0 : new BigNumber(value).shiftedBy(asset.decimals).toNumber()
}
function demagnify(amount: number, asset: Asset) {
return amount === 0 ? 0 : new BigNumber(amount).dividedBy(-1 * asset.decimals).toNumber()
}
export default function NumberInput(props: Props) { export default function NumberInput(props: Props) {
const inputRef = React.useRef<HTMLInputElement>(null) const inputRef = React.useRef<HTMLInputElement>(null)
const cursorRef = React.useRef(0) const cursorRef = React.useRef(0)
const max = props.max ? demagnify(props.max, props.asset) : undefined // const max = props.max ? demagnify(props.max, props.asset) : undefined
const [inputValue, setInputValue] = useState({ const [formattedAmount, setFormattedAmount] = useState(
formatted: demagnify(props.amount, props.asset).toString(), props.amount.shiftedBy(-1 * props.asset.decimals).toString(),
value: demagnify(props.amount, props.asset), )
})
useEffect(() => { useEffect(() => {
setInputValue({ setFormattedAmount(
formatted: demagnify(props.amount, props.asset).toString(), formatValue(props.amount.toNumber(), {
value: demagnify(props.amount, props.asset), decimals: props.asset.decimals,
}) maxDecimals: props.asset.decimals,
thousandSeparator: false,
}),
)
}, [props.amount, props.asset]) }, [props.amount, props.asset])
useEffect(() => { useEffect(() => {
@ -58,16 +54,17 @@ export default function NumberInput(props: Props) {
props.onFocus && props.onFocus() props.onFocus && props.onFocus()
} }
const updateValues = (formatted: string, value: number) => { const updateValues = (formatted: string, amount: BigNumber) => {
const lastChar = formatted.charAt(formatted.length - 1) const lastChar = formatted.charAt(formatted.length - 1)
if (lastChar === '.') { if (lastChar === '.') {
cursorRef.current = (inputRef.current?.selectionEnd || 0) + 1 cursorRef.current = (inputRef.current?.selectionEnd || 0) + 1
} else { } else {
cursorRef.current = inputRef.current?.selectionEnd || 0 cursorRef.current = inputRef.current?.selectionEnd || 0
} }
setInputValue({ formatted, value }) setFormattedAmount(formatted)
if (value !== inputValue.value) { console.log(props.amount.toNumber(), amount.toNumber())
props.onChange(magnify(value, props.asset)) if (props.amount.isEqualTo(amount)) {
props.onChange(amount)
} }
} }
@ -75,7 +72,7 @@ export default function NumberInput(props: Props) {
if (!inputRef.current) return if (!inputRef.current) return
const cursor = cursorRef.current const cursor = cursorRef.current
const length = inputValue.formatted.length const length = formattedAmount.length
if (cursor > length) { if (cursor > length) {
inputRef.current.setSelectionRange(length, length) inputRef.current.setSelectionRange(length, length)
@ -83,70 +80,72 @@ export default function NumberInput(props: Props) {
} }
inputRef.current.setSelectionRange(cursor, cursor) inputRef.current.setSelectionRange(cursor, cursor)
}, [inputValue, inputRef]) }, [formattedAmount, inputRef])
const onInputChange = (value: string) => { const onInputChange = (formattedAmount: string) => {
const numberCount = value.match(/[0-9]/g)?.length || 0 const numberCount = formattedAmount.match(/[0-9]/g)?.length || 0
const decimals = value.split('.')[1]?.length || 0 const decimals = formattedAmount.split('.')[1]?.length || 0
const lastChar = value.charAt(value.length - 1) const lastChar = formattedAmount.charAt(formattedAmount.length - 1)
const isNumber = !isNaN(Number(value)) const isNumber = !isNaN(Number(formattedAmount))
const hasMultipleDots = (value.match(/[.,]/g)?.length || 0) > 1 const hasMultipleDots = (formattedAmount.match(/[.,]/g)?.length || 0) > 1
const isSeparator = lastChar === '.' || lastChar === ',' const isSeparator = lastChar === '.' || lastChar === ','
const isNegative = value.indexOf('-') > -1 const isNegative = formattedAmount.indexOf('-') > -1
const isLowerThanMinimum = props.minValue !== undefined && Number(value) < props.minValue const isLowerThanMinimum = props.min !== undefined && props.min.isGreaterThan(formattedAmount)
const isHigherThanMaximum = max !== undefined && Number(value) > max const isHigherThanMaximum = props.max !== undefined && props.max.isLessThan(formattedAmount)
const isTooLong = props.maxLength !== undefined && numberCount > props.maxLength const isTooLong = props.maxLength !== undefined && numberCount > props.maxLength
const exceedsMaxDecimals = props.maxDecimals !== undefined && decimals > props.maxDecimals const exceedsMaxDecimals = props.maxDecimals !== undefined && decimals > props.maxDecimals
if (isNegative && !props.allowNegative) return if (isNegative && !props.allowNegative) return
if (isSeparator && value.length === 1) { if (isSeparator && formattedAmount.length === 1) {
updateValues('0.', 0) updateValues('0.', BN(0))
return return
} }
if (isSeparator && !hasMultipleDots) { if (isSeparator && !hasMultipleDots) {
updateValues(value.replace(',', '.'), inputValue.value) updateValues(formattedAmount.replace(',', '.'), props.amount)
return return
} }
if (!isNumber || hasMultipleDots) return if (!isNumber || hasMultipleDots) return
if (exceedsMaxDecimals) { if (exceedsMaxDecimals) {
value = value.substring(0, value.length - 1) formattedAmount = formattedAmount.substring(0, formattedAmount.length - 1)
} }
if (isTooLong) return if (isTooLong) return
if (isLowerThanMinimum) { if (isLowerThanMinimum && props.min) {
updateValues(String(props.minValue), props.minValue!) updateValues(String(props.min), props.min)
return return
} }
if (isHigherThanMaximum) { if (isHigherThanMaximum && props.max) {
updateValues(String(max), max!) updateValues(String(props.max), props.max)
return return
} }
if (lastChar === '0' && Number(value) === Number(inputValue.value)) { const amount = BN(formattedAmount).shiftedBy(props.asset.decimals)
if (lastChar === '0' && amount.isEqualTo(props.amount)) {
cursorRef.current = (inputRef.current?.selectionEnd || 0) + 1 cursorRef.current = (inputRef.current?.selectionEnd || 0) + 1
setInputValue({ ...inputValue, formatted: value }) setFormattedAmount(formattedAmount)
return return
} }
if (!value) { if (!formattedAmount) {
updateValues(value, 0) updateValues(formattedAmount, BN(0))
return return
} }
updateValues(String(Number(value)), Number(value)) updateValues(formattedAmount, amount)
} }
return ( return (
<input <input
ref={inputRef} ref={inputRef}
type='text' type='text'
value={inputValue.formatted === '0' ? '' : inputValue.formatted} value={formattedAmount === '0' ? '' : formattedAmount}
onFocus={onInputFocus} onFocus={onInputFocus}
onChange={(e) => onInputChange(e.target.value)} onChange={(e) => onInputChange(e.target.value)}
onBlur={props.onBlur} onBlur={props.onBlur}

View File

@ -5,25 +5,27 @@ interface Props {
children?: ReactNode | string children?: ReactNode | string
content?: ReactNode | string content?: ReactNode | string
className?: string className?: string
hasBackdropIsolation?: boolean
show: boolean show: boolean
setShow: (show: boolean) => void setShow: (show: boolean) => void
} }
export const Overlay = ({ children, content, className, show, setShow }: Props) => { export default function Overlay(props: Props) {
const onClickAway = () => { const onClickAway = () => {
setShow(false) props.setShow(false)
} }
return show ? ( return props.show ? (
<> <>
<div <div
className={classNames( className={classNames(
'max-w-screen absolute isolate z-50 rounded-sm shadow-overlay backdrop-blur-lg gradient-popover', 'max-w-screen absolute isolate z-50 rounded-sm shadow-overlay backdrop-blur-lg',
props.hasBackdropIsolation ? 'bg-body' : 'gradient-popover',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-sm before:p-[1px] before:border-glas', 'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-sm before:p-[1px] before:border-glas',
className, props.className,
)} )}
> >
{children ? children : content} {props.children ? props.children : props.content}
</div> </div>
<div <div
className='fixed left-0 top-0 z-40 block h-full w-full hover:cursor-pointer' className='fixed left-0 top-0 z-40 block h-full w-full hover:cursor-pointer'

View File

@ -3,7 +3,7 @@ import { Suspense } from 'react'
import Card from 'components/Card' import Card from 'components/Card'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import { Text } from 'components/Text' import Text from 'components/Text'
import { getAccounts } from 'utils/api' import { getAccounts } from 'utils/api'
async function Content(props: PageProps) { async function Content(props: PageProps) {

View File

@ -0,0 +1,75 @@
import classNames from 'classnames'
import Image from 'next/image'
import DisplayCurrency from 'components/DisplayCurrency'
import Text from 'components/Text'
import { ASSETS } from 'constants/assets'
import { formatValue } from 'utils/formatters'
interface Props extends Option {
isSelected?: boolean
isDisplay?: boolean
onClick?: (value: string) => void
}
export default function Option(props: Props) {
const isCoin = !!props.denom
if (isCoin) {
const currentAsset = ASSETS.find((asset) => asset.denom === props.denom)
const symbol = currentAsset?.symbol ?? ASSETS[0].symbol
const logo = currentAsset?.logo ?? ASSETS[0].logo
const denom = currentAsset?.denom ?? ASSETS[0].denom
const decimals = currentAsset?.decimals ?? ASSETS[0].decimals
const balance = props.amount ?? '0'
if (props.isDisplay) {
return (
<div className={classNames('block bg-white/10 p-3 hover:cursor-pointer')}>{symbol}</div>
)
}
return (
<div
className={classNames(
'grid grid-flow-row grid-cols-5 grid-rows-2 py-3.5 pr-4',
'border-b border-b-white/20 last:border-none',
'hover:cursor-pointer hover:bg-white/20',
props.isSelected && 'bg-white/10',
)}
onClick={() => props?.onClick && props.onClick(denom)}
>
<div className='row-span-2 flex h-full items-center justify-center'>
<Image src={logo} alt={`${symbol} token logo`} width={32} height={32} />
</div>
<Text className='col-span-2 pb-1'>{symbol}</Text>
<Text size='sm' className='col-span-2 pb-1 text-right font-bold'>
{formatValue(balance, { decimals, maxDecimals: 4, minDecimals: 0, rounded: true })}
</Text>
<Text size='sm' className='col-span-2 text-white/50'>
{formatValue(5, { maxDecimals: 2, minDecimals: 0, prefix: 'APY ', suffix: '%' })}
</Text>
<Text size='sm' className='col-span-2 text-right text-white/50'>
<DisplayCurrency coin={{ denom, amount: balance }} />
</Text>
</div>
)
}
const label = props.label
if (props.isDisplay) {
return <div className={classNames('block bg-white/10 p-3 hover:cursor-pointer')}>{label}</div>
}
return (
<div
className={classNames(
'block p-3 hover:cursor-pointer hover:bg-white/20',
props.isSelected && 'bg-white/10',
)}
onClick={() => props?.onClick && props.onClick(props.value)}
>
{label}
</div>
)
}

View File

@ -0,0 +1,82 @@
'use client'
import classNames from 'classnames'
import { useState } from 'react'
import Overlay from 'components/Overlay/Overlay'
import Option from 'components/Select/Option'
import Text from 'components/Text'
import useToggle from 'hooks/useToggle'
interface Props {
options: Option[]
defaultValue?: string
onChange: (value: string) => void
isParent?: boolean
className?: string
title?: string
}
export default function Select(props: Props) {
const [value, setValue] = useState(props.defaultValue)
const selectedOption = value
? props.options.find((option) => option?.value || option?.denom === value)
: null
const [selected, setSelected] = useState<Option>(selectedOption)
const [showDropdown, setShowDropdown] = useToggle()
function handleChange(optionValue: string) {
setValue(optionValue)
setSelected(props.options.find((option) => option?.value || option?.denom === optionValue))
setShowDropdown(false)
props.onChange(optionValue)
}
console.log(selected)
return (
<div
className={classNames(
props.isParent && 'relative',
'flex min-w-fit items-center gap-2',
props.className,
)}
role='select'
onClick={() => setShowDropdown(!showDropdown)}
>
{selectedOption ? (
<Option {...selectedOption} isDisplay />
) : (
<Text className='w-full opacity-50 hover:cursor-pointer'>Select</Text>
)}
<Overlay
show={showDropdown}
className={classNames('left-0 top-[calc(100%+8px)] isolate w-full')}
setShow={setShowDropdown}
hasBackdropIsolation
>
<div className='relative isolate w-full overflow-hidden rounded-sm'>
{props.title && (
<Text size='lg' className='block bg-white/25 p-4 font-bold'>
{props.title}
</Text>
)}
{props.options.map((option: Option, index: number) => (
<Option
key={index}
{...option}
isSelected={
option?.value
? option?.value === selected?.value
: option?.denom === selected?.denom
}
onClick={handleChange}
/>
))}
</div>
</Overlay>
</div>
)
}

View File

@ -1,23 +1,22 @@
'use client' 'use client'
import { useState } from 'react'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { Gear } from 'components/Icons' import { Gear } from 'components/Icons'
import { Overlay } from 'components/Overlay/Overlay' import Overlay from 'components/Overlay/Overlay'
import Switch from 'components/Switch' import Switch from 'components/Switch'
import { Text } from 'components/Text' import Text from 'components/Text'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import { ASSETS } from 'constants/assets'
import { DISPLAY_CURRENCY_KEY, ENABLE_ANIMATIONS_KEY } from 'constants/localStore' import { DISPLAY_CURRENCY_KEY, ENABLE_ANIMATIONS_KEY } from 'constants/localStore'
import { useAnimations } from 'hooks/useAnimations' import { useAnimations } from 'hooks/useAnimations'
import useToggle from 'hooks/useToggle'
import useStore from 'store' import useStore from 'store'
import { getDisplayCurrencies } from 'utils/assets' import { getDisplayCurrencies } from 'utils/assets'
import { ASSETS } from 'constants/assets'
export default function Settings() { export default function Settings() {
useAnimations() useAnimations()
const [showMenu, setShowMenu] = useState(false) const [showMenu, setShowMenu] = useToggle()
const enableAnimations = useStore((s) => s.enableAnimations) const enableAnimations = useStore((s) => s.enableAnimations)
const displayCurrency = useStore((s) => s.displayCurrency) const displayCurrency = useStore((s) => s.displayCurrency)
const displayCurrencies = getDisplayCurrencies() const displayCurrencies = getDisplayCurrencies()
@ -32,10 +31,10 @@ export default function Settings() {
} }
} }
function handleReduceMotion(val: boolean) { function handleReduceMotion() {
useStore.setState({ enableAnimations: !val }) useStore.setState({ enableAnimations: !enableAnimations })
if (typeof window !== 'undefined') if (typeof window !== 'undefined')
window.localStorage.setItem(ENABLE_ANIMATIONS_KEY, val ? 'false' : 'true') window.localStorage.setItem(ENABLE_ANIMATIONS_KEY, enableAnimations ? 'false' : 'true')
} }
function handleCurrencyChange(e: React.ChangeEvent<HTMLSelectElement>) { function handleCurrencyChange(e: React.ChangeEvent<HTMLSelectElement>) {

View File

@ -3,6 +3,7 @@ import { ChangeEvent, useRef, useState } from 'react'
import Draggable from 'react-draggable' import Draggable from 'react-draggable'
import { OverlayMark } from 'components/Icons/index' import { OverlayMark } from 'components/Icons/index'
import useToggle from 'hooks/useToggle'
type Props = { type Props = {
value: number value: number
@ -12,11 +13,11 @@ type Props = {
} }
export default function Slider(props: Props) { export default function Slider(props: Props) {
const [showTooltip, setShowTooltip] = useState(false) const [showTooltip, setShowTooltip] = useToggle()
const [sliderRect, setSliderRect] = useState({ width: 0, left: 0, right: 0 }) const [sliderRect, setSliderRect] = useState({ width: 0, left: 0, right: 0 })
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
const nodeRef = useRef(null) const nodeRef = useRef(null)
const [isDragging, setIsDragging] = useState(false) const [isDragging, setIsDragging] = useToggle()
function handleSliderRect() { function handleSliderRect() {
const leftCap = ref.current?.getBoundingClientRect().left ?? 0 const leftCap = ref.current?.getBoundingClientRect().left ?? 0

View File

@ -3,7 +3,7 @@ import classNames from 'classnames'
interface Props { interface Props {
name: string name: string
checked: boolean checked: boolean
onChange: (checked: boolean) => void onChange: () => void
className?: string className?: string
disabled?: boolean disabled?: boolean
} }
@ -23,7 +23,7 @@ export default function Switch(props: Props) {
name={props.name} name={props.name}
className={classNames('peer hidden')} className={classNames('peer hidden')}
checked={props.checked} checked={props.checked}
onChange={(e) => props.onChange(e.target.checked)} onChange={props.onChange}
/> />
<label <label
htmlFor={props.name} htmlFor={props.name}

View File

@ -1,14 +1,14 @@
import classNames from 'classnames' import classNames from 'classnames'
import Switch from 'components/Switch' import Switch from 'components/Switch'
import { Text } from 'components/Text' import Text from 'components/Text'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
interface Props { interface Props {
name: string name: string
label: string label: string
value: boolean value: boolean
onChange: (checked: boolean) => void onChange: () => void
className?: string className?: string
tooltip?: string tooltip?: string
disabled?: boolean disabled?: boolean

View File

@ -13,14 +13,10 @@ interface Props {
const headlines = ['h1', 'h2', 'h3', 'h4'] const headlines = ['h1', 'h2', 'h3', 'h4']
const headMap = ['6xl', '5xl', '4xl', '3xl'] const headMap = ['6xl', '5xl', '4xl', '3xl']
export const Text = ({ export default function Text(props: Props) {
children, const tag = props.tag ?? 'p'
className, const size = props.size ?? 'base'
monospace = false,
size = 'base',
tag = 'p',
uppercase = false,
}: Props) => {
const tagIndex = headlines.indexOf(tag) const tagIndex = headlines.indexOf(tag)
const sizeClass = tagIndex > -1 ? headMap[tagIndex] : size const sizeClass = tagIndex > -1 ? headMap[tagIndex] : size
const HtmlElement = tag as keyof JSX.IntrinsicElements const HtmlElement = tag as keyof JSX.IntrinsicElements
@ -28,12 +24,12 @@ export const Text = ({
return ( return (
<HtmlElement <HtmlElement
className={classNames( className={classNames(
className, props.className,
uppercase ? `text-${sizeClass}-caps` : `text-${sizeClass}`, props.uppercase ? `text-${sizeClass}-caps` : `text-${sizeClass}`,
monospace && 'number', props.monospace && 'number',
)} )}
> >
{children} {props.children}
</HtmlElement> </HtmlElement>
) )
} }

View File

@ -1,4 +1,4 @@
import { Text } from 'components/Text' import Text from 'components/Text'
interface Props { interface Props {
title: string | React.ReactNode title: string | React.ReactNode

View File

@ -5,7 +5,7 @@ import { toast as createToast, Slide, ToastContainer } from 'react-toastify'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { CheckCircled, Cross, CrossCircled } from 'components/Icons' import { CheckCircled, Cross, CrossCircled } from 'components/Icons'
import { Text } from 'components/Text' import Text from 'components/Text'
import useStore from 'store' import useStore from 'store'
export default function Toaster() { export default function Toaster() {
@ -40,7 +40,7 @@ export default function Toaster() {
</Text> </Text>
</div> </div>
<Text size='sm' className='text-bold text-white'> <Text size='sm' className='font-bold text-white'>
{toast.message} {toast.message}
</Text> </Text>
<div className='absolute right-6 top-8 '> <div className='absolute right-6 top-8 '>

View File

@ -1,19 +1,82 @@
'use client'
import BigNumber from 'bignumber.js'
import classNames from 'classnames' import classNames from 'classnames'
import Image from 'next/image' import Image from 'next/image'
import { useCallback, useEffect, useState } from 'react'
import DisplayCurrency from 'components/DisplayCurrency'
import NumberInput from 'components/NumberInput' import NumberInput from 'components/NumberInput'
import { Text } from 'components/Text' import Select from 'components/Select/Select'
import Text from 'components/Text'
import { ASSETS } from 'constants/assets'
import useStore from 'store'
import { magnify } from 'utils/formatters'
import { BN } from 'utils/helpers'
interface Props { interface Props {
amount: number amount: BigNumber
max: number onChange: (amount: BigNumber) => void
asset: Asset
onChange: (amount: number) => void
className?: string className?: string
disabled?: boolean disabled?: boolean
} }
export default function TokenInput(props: Props) { interface SingleProps extends Props {
asset: Asset
max: BigNumber
hasSelect?: boolean
onChangeAsset?: (asset: Asset, max: BigNumber) => void
}
interface SelectProps extends Props {
asset?: Asset
max?: BigNumber
hasSelect: boolean
onChangeAsset: (asset: Asset, max: BigNumber) => void
}
export default function TokenInput(props: SingleProps | SelectProps) {
const balances = useStore((s) => s.balances)
const baseCurrency = useStore((s) => s.baseCurrency)
const [asset, setAsset] = useState<Asset>(props.asset ? props.asset : baseCurrency)
const [coin, setCoin] = useState<Coin>({
denom: props.asset ? props.asset.denom : baseCurrency.denom,
amount: '0',
})
const selectedAssetDenom = props.asset ? props.asset.denom : baseCurrency.denom
const updateAsset = useCallback(
(coinDenom: string) => {
const newAsset = ASSETS.find((asset) => asset.denom === coinDenom) ?? baseCurrency
const newCoin = balances?.find((coin) => coin.denom === coinDenom)
setAsset(newAsset)
setCoin(newCoin ?? { denom: coinDenom, amount: '0' })
},
[balances, baseCurrency],
)
function setDefaultAsset() {
if (!balances || balances?.length === 0) return setAsset(baseCurrency)
if (balances.length === 1)
return setAsset(ASSETS.find((asset) => asset.denom === balances[0].denom) ?? baseCurrency)
return setAsset(ASSETS.find((asset) => asset.denom === selectedAssetDenom) ?? baseCurrency)
}
useEffect(
() => {
setDefaultAsset()
updateAsset(asset.denom)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
)
useEffect(() => {
props.onChangeAsset && props.onChangeAsset(asset, coin ? BN(coin.amount) : BN(0))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [coin, asset])
return ( return (
<div <div
className={classNames( className={classNames(
@ -22,28 +85,49 @@ export default function TokenInput(props: Props) {
props.disabled && 'pointer-events-none opacity-50', props.disabled && 'pointer-events-none opacity-50',
)} )}
> >
<div className='box-content flex h-11 w-full rounded-sm border border-white/20 bg-white/5'> <div className='relative isolate z-40 box-content flex h-11 w-full rounded-sm border border-white/20 bg-white/5'>
<div className='flex min-w-fit items-center gap-2 border-r border-white/20 bg-white/5 p-3'> {props.hasSelect && balances ? (
<Image src={props.asset.logo} alt='token' width={20} height={20} /> <Select
<Text>{props.asset.symbol}</Text> options={balances}
</div> defaultValue={coin.denom}
onChange={(value) => updateAsset(value)}
title='Your Wallet'
className=' border-r border-white/20 bg-white/5'
/>
) : (
<div className='flex min-w-fit items-center gap-2 border-r border-white/20 bg-white/5 p-3'>
<Image src={asset.logo} alt='token' width={20} height={20} />
<Text>{asset.symbol}</Text>
</div>
)}
<NumberInput <NumberInput
disabled={props.disabled} disabled={props.disabled}
asset={props.asset} asset={asset}
maxDecimals={props.asset.decimals} maxDecimals={asset.decimals}
onChange={props.onChange} onChange={props.onChange}
amount={props.amount} amount={props.amount}
max={props.max} max={props.max}
className='border-none p-3' className='border-none p-3'
/> />
</div> </div>
<div className='flex justify-between'>
<Text size='xs' className='text-white/50' monospace> <div className='flex'>
1 OSMO = $0.9977 <div className='flex flex-1'>
</Text> <Text size='xs' className='text-white/50' monospace>
<Text size='xs' monospace className='text-white/50'> {`1 ${asset.symbol} =`}
~ $0.00 </Text>
</Text> <DisplayCurrency
className='inline pl-0.5 text-xs text-white/50'
coin={{ denom: asset.denom, amount: String(magnify(1, asset)) }}
/>
</div>
<div className='flex'>
<DisplayCurrency
isApproximation
className='inline pl-0.5 text-xs text-white/50'
coin={{ denom: asset.denom, amount: props.amount.toString() }}
/>
</div>
</div> </div>
</div> </div>
) )

View File

@ -1,25 +1,41 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import Slider from './Slider' import { BN } from 'utils/helpers'
import TokenInput from './TokenInput' import Slider from 'components/Slider'
import TokenInput from 'components/TokenInput'
import { ASSETS } from 'constants/assets'
interface Props { interface Props {
amount: number amount: BigNumber
max: number onChange: (amount: BigNumber) => void
asset: Asset
onChange: (amount: number) => void
className?: string className?: string
disabled?: boolean disabled?: boolean
} }
export default function TokenInputWithSlider(props: Props) { interface SingleProps extends Props {
max: BigNumber
asset: Asset
hasSelect?: boolean
onChangeAsset?: (asset: Asset) => void
}
interface SelectProps extends Props {
max?: BigNumber
asset?: Asset
onChangeAsset: (asset: Asset) => void
hasSelect: boolean
}
export default function TokenInputWithSlider(props: SingleProps | SelectProps) {
const [amount, setAmount] = useState(props.amount) const [amount, setAmount] = useState(props.amount)
const [percentage, setPercentage] = useState(0) const [percentage, setPercentage] = useState(0)
const [asset, setAsset] = useState<Asset>(props.asset ? props.asset : ASSETS[0])
const [max, setMax] = useState<BigNumber>(props.max ? props.max : BN(0))
const onSliderChange = useCallback( const onSliderChange = useCallback(
(percentage: number, liquidityAmount: number) => { (percentage: number, liquidityAmount: BigNumber) => {
const newAmount = new BigNumber(percentage).div(100).times(liquidityAmount).toNumber() const newAmount = BN(percentage).div(100).times(liquidityAmount)
setPercentage(percentage) setPercentage(percentage)
setAmount(newAmount) setAmount(newAmount)
props.onChange(newAmount) props.onChange(newAmount)
@ -28,27 +44,40 @@ export default function TokenInputWithSlider(props: Props) {
) )
const onInputChange = useCallback( const onInputChange = useCallback(
(newAmount: number, liquidityAmount: number) => { (newAmount: BigNumber, liquidityAmount: BigNumber) => {
setAmount(newAmount) setAmount(newAmount)
setPercentage(new BigNumber(newAmount).div(liquidityAmount).times(100).toNumber()) setPercentage(BN(newAmount).div(liquidityAmount).times(100).toNumber())
props.onChange(newAmount) props.onChange(newAmount)
}, },
[props], [props],
) )
const onAssetChange = useCallback(
(newAsset: Asset, liquidtyAmount: BigNumber) => {
props.onChangeAsset && props.onChangeAsset(newAsset)
setAsset(newAsset)
setMax(liquidtyAmount)
setPercentage(0)
setAmount(BN(0))
},
[props],
)
return ( return (
<div className={props.className}> <div className={props.className}>
<TokenInput <TokenInput
asset={props.asset} asset={asset}
onChange={(amount) => onInputChange(amount, props.max)} onChange={(amount) => onInputChange(amount, max)}
onChangeAsset={(asset: Asset, max: BigNumber) => onAssetChange(asset, max)}
amount={amount} amount={amount}
max={props.max} max={max}
className='mb-4' className='mb-4'
disabled={props.disabled} disabled={props.disabled}
hasSelect
/> />
<Slider <Slider
value={percentage} value={percentage}
onChange={(value) => onSliderChange(value, props.max)} onChange={(value) => onSliderChange(value, max)}
disabled={props.disabled} disabled={props.disabled}
/> />
</div> </div>

View File

@ -2,7 +2,7 @@ import { Suspense } from 'react'
import Card from 'components/Card' import Card from 'components/Card'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import { Text } from 'components/Text' import Text from 'components/Text'
async function Content(props: PageProps) { async function Content(props: PageProps) {
const address = props.params.address const address = props.params.address

View File

@ -2,7 +2,7 @@ import { Suspense } from 'react'
import Card from 'components/Card' import Card from 'components/Card'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import { Text } from 'components/Text' import Text from 'components/Text'
async function Content(props: PageProps) { async function Content(props: PageProps) {
const address = props.params.address const address = props.params.address

View File

@ -2,7 +2,7 @@ import { Suspense } from 'react'
import Card from 'components/Card' import Card from 'components/Card'
import Loading from 'components/Loading' import Loading from 'components/Loading'
import { Text } from 'components/Text' import Text from 'components/Text'
async function Content(props: PageProps) { async function Content(props: PageProps) {
return <Text size='sm'>Chart view</Text> return <Text size='sm'>Chart view</Text>

View File

@ -16,17 +16,19 @@ import { Button } from 'components/Button'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { Check, Copy, ExternalLink, Osmo } from 'components/Icons' import { Check, Copy, ExternalLink, Osmo } from 'components/Icons'
import { Overlay } from 'components/Overlay/Overlay' import Overlay from 'components/Overlay/Overlay'
import { Text } from 'components/Text' import Text from 'components/Text'
import useToggle from 'hooks/useToggle'
import useStore from 'store' import useStore from 'store'
import { Endpoints, getEndpoint, getWalletBalancesSWR } from 'utils/api' import { Endpoints, getEndpoint, getWalletBalancesSWR } from 'utils/api'
import { getBaseAsset } from 'utils/assets' import { getBaseAsset, getMarketAssets } from 'utils/assets'
import { formatValue, truncate } from 'utils/formatters' import { formatValue, truncate } from 'utils/formatters'
export default function ConnectedButton() { export default function ConnectedButton() {
// --------------- // ---------------
// EXTERNAL HOOKS // EXTERNAL HOOKS
// --------------- // ---------------
const marketAssets = getMarketAssets()
const { disconnect } = useWallet() const { disconnect } = useWallet()
const { disconnect: terminate } = useWalletManager() const { disconnect: terminate } = useWalletManager()
const address = useStore((s) => s.client?.recentWallet.account?.address) const address = useStore((s) => s.client?.recentWallet.account?.address)
@ -40,7 +42,7 @@ export default function ConnectedButton() {
// --------------- // ---------------
// LOCAL STATE // LOCAL STATE
// --------------- // ---------------
const [showDetails, setShowDetails] = useState(false) const [showDetails, setShowDetails] = useToggle()
const [walletAmount, setWalletAmount] = useState(0) const [walletAmount, setWalletAmount] = useState(0)
const [isCopied, setCopied] = useClipboard(address || '', { const [isCopied, setCopied] = useClipboard(address || '', {
successDuration: 1000 * 5, successDuration: 1000 * 5,
@ -59,7 +61,7 @@ export default function ConnectedButton() {
const disconnectWallet = () => { const disconnectWallet = () => {
disconnect() disconnect()
terminate() terminate()
useStore.setState({ client: undefined }) useStore.setState({ client: undefined, balances: null })
} }
useEffect(() => { useEffect(() => {
@ -69,7 +71,11 @@ export default function ConnectedButton() {
.div(10 ** baseAsset.decimals) .div(10 ** baseAsset.decimals)
.toNumber(), .toNumber(),
) )
}, [data, baseAsset.denom, baseAsset.decimals])
const assetDenoms = marketAssets.map((asset) => asset.denom)
const balances = data.filter((coin) => assetDenoms.includes(coin.denom))
useStore.setState({ balances })
}, [data, baseAsset.denom, baseAsset.decimals, marketAssets])
return ( return (
<div className={'relative'}> <div className={'relative'}>
@ -102,7 +108,7 @@ export default function ConnectedButton() {
{isLoading ? ( {isLoading ? (
<CircularProgress size={12} /> <CircularProgress size={12} />
) : ( ) : (
`${formatValue(walletAmount, { suffix: baseAsset.symbol })}` `${formatValue(walletAmount, { suffix: ` ${baseAsset.symbol}` })}`
)} )}
</div> </div>
</Button> </Button>

View File

@ -1,13 +1,13 @@
import Card from 'components/Card'
import Tab from 'components/Earn/Tab' import Tab from 'components/Earn/Tab'
import AvailableVaults from 'components/Earn/vault/AvailableVaults'
import FeaturedVaults from 'components/Earn/vault/FeaturedVaults'
export default function Farmpage({ params }: { params: PageParams }) { export default function Farmpage({ params }: { params: PageParams }) {
return ( return (
<> <>
<Tab params={params} isFarm /> <Tab params={params} isFarm />
<Card title='Featured vaults'> <FeaturedVaults />
<></> <AvailableVaults />
</Card>
</> </>
) )
} }

14
src/hooks/useToggle.tsx Normal file
View File

@ -0,0 +1,14 @@
import { useState } from 'react'
export default function useToggle(
defaultValue?: boolean,
): [boolean, (isToggled?: boolean) => void] {
const [toggle, setToggle] = useState<boolean>(defaultValue ?? false)
function handleToggle(isToggled?: boolean) {
if (isToggled !== undefined) return setToggle(isToggled)
return setToggle(!toggle)
}
return [toggle, handleToggle]
}

View File

@ -0,0 +1,21 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { ENV, ENV_MISSING_MESSAGE, VERCEL_BYPASS } from 'constants/env'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!ENV.URL_RPC || !ENV.ADDRESS_CREDIT_MANAGER || !ENV.URL_API) {
return res.status(404).json(ENV_MISSING_MESSAGE)
}
const accountId = req.query.id
const account: Account = await (
await fetch(`${ENV.URL_API}/accounts/${accountId}${VERCEL_BYPASS}`)
).json()
if (account) {
return res.status(200).json(account.vaults)
}
return res.status(404)
}

View File

@ -1,7 +1,7 @@
import BigNumber from 'bignumber.js'
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { ENV, ENV_MISSING_MESSAGE, VERCEL_BYPASS } from 'constants/env' import { ENV, ENV_MISSING_MESSAGE, VERCEL_BYPASS } from 'constants/env'
import { BN } from 'utils/helpers'
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!ENV.URL_API) { if (!ENV.URL_API) {
@ -28,7 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
borrowRate: market.borrowRate ?? 0, borrowRate: market.borrowRate ?? 0,
liquidity: { liquidity: {
amount: amount, amount: amount,
value: new BigNumber(amount).times(price).toString(), value: BN(amount).times(price).toString(),
}, },
} }
}) })

View File

@ -1,4 +1,3 @@
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { WalletClient, WalletConnectionStatus } from '@marsprotocol/wallet-connector' import { WalletClient, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
import { GetState, SetState } from 'zustand' import { GetState, SetState } from 'zustand'
@ -7,6 +6,7 @@ export interface CommonSlice {
address?: string address?: string
enableAnimations: boolean enableAnimations: boolean
isOpen: boolean isOpen: boolean
balances: Coin[] | null
selectedAccount: string | null selectedAccount: string | null
client?: WalletClient client?: WalletClient
status: WalletConnectionStatus status: WalletConnectionStatus
@ -15,6 +15,7 @@ export interface CommonSlice {
export function createCommonSlice(set: SetState<CommonSlice>, get: GetState<CommonSlice>) { export function createCommonSlice(set: SetState<CommonSlice>, get: GetState<CommonSlice>) {
return { return {
accounts: null, accounts: null,
balances: null,
creditAccounts: null, creditAccounts: null,
enableAnimations: true, enableAnimations: true,
isOpen: true, isOpen: true,

View File

@ -10,6 +10,9 @@ export interface ModalSlice {
deleteAccountModal: boolean deleteAccountModal: boolean
fundAccountModal: boolean fundAccountModal: boolean
withdrawModal: boolean withdrawModal: boolean
vaultModal: {
vault: Vault
} | null
} }
export function createModalSlice(set: SetState<ModalSlice>, get: GetState<ModalSlice>) { export function createModalSlice(set: SetState<ModalSlice>, get: GetState<ModalSlice>) {
@ -19,5 +22,6 @@ export function createModalSlice(set: SetState<ModalSlice>, get: GetState<ModalS
deleteAccountModal: false, deleteAccountModal: false,
fundAccountModal: false, fundAccountModal: false,
withdrawModal: false, withdrawModal: false,
vaultModal: null,
} }
} }

View File

@ -3,4 +3,5 @@ interface Account {
deposits: Coin[] deposits: Coin[]
debts: Coin[] debts: Coin[]
lends: Coin[] lends: Coin[]
vaults: import('types/generated/mars-mock-credit-manager/MarsMockCreditManager.types').ArrayOfVaultInfoResponse
} }

1
src/types/interfaces/option.d.ts vendored Normal file
View File

@ -0,0 +1 @@
type Option = import('cosmjs/stargate').Coin | { value: string; label: string }

View File

@ -3,6 +3,7 @@ interface AccountResponse {
deposits: Coin[] deposits: Coin[]
debts: Coin[] debts: Coin[]
lends: Coin[] lends: Coin[]
vaults: import('types/generated/mars-mock-credit-manager/MarsMockCreditManager.types').ArrayOfVaultInfoResponse
} }
interface MarketResponse { interface MarketResponse {

View File

@ -33,3 +33,16 @@ interface VaultConfig extends VaultMetaData {
interface Vault extends VaultConfig { interface Vault extends VaultConfig {
apy: number | null apy: number | null
} }
interface ActiveVault extends Vault {
status: 'active' | 'unlocking' | 'unlocked'
amounts: {
primary: number
secondary: number
}
values: {
primary: number
secondary: number
}
unlocksAt?: number
}

View File

@ -1,17 +1,23 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { BN } from './helpers'
export const calculateAccountBalance = (account: Account, prices: Coin[]) => { export const calculateAccountBalance = (account: Account, prices: Coin[]) => {
const totalDepositValue = account.deposits.reduce((acc, deposit) => { const totalDepositValue = account.deposits.reduce((acc, deposit) => {
const price = prices.find((price) => price.denom === deposit.denom)?.amount ?? 0 const price = prices.find((price) => price.denom === deposit.denom)?.amount ?? 0
const depositValue = new BigNumber(deposit.amount).multipliedBy(price) const depositValue = BN(deposit.amount).multipliedBy(price)
return acc.plus(depositValue) return acc.plus(depositValue)
}, new BigNumber(0)) }, BN(0))
const totalDebtValue = account.debts.reduce((acc, debt) => { const totalDebtValue = account.debts.reduce((acc, debt) => {
const price = prices.find((price) => price.denom === debt.denom)?.amount ?? 0 const price = prices.find((price) => price.denom === debt.denom)?.amount ?? 0
const debtValue = new BigNumber(debt.amount).multipliedBy(price) const debtValue = BN(debt.amount).multipliedBy(price)
return acc.plus(debtValue) return acc.plus(debtValue)
}, new BigNumber(0)) }, BN(0))
return totalDepositValue.minus(totalDebtValue).toNumber() return totalDepositValue.minus(totalDebtValue).toNumber()
} }
export function getAmount(denom: string, coins: Coin[]) {
return BN(coins.find((asset) => asset.denom === denom)?.amount ?? 0)
}

View File

@ -1,6 +1,5 @@
import BigNumber from 'bignumber.js'
import { getMarketAssets } from './assets' import { getMarketAssets } from './assets'
import { BN } from './helpers'
export function truncate(text = '', [h, t]: [number, number] = [6, 6]): string { export function truncate(text = '', [h, t]: [number, number] = [6, 6]): string {
const head = text.slice(0, h) const head = text.slice(0, h)
@ -33,7 +32,7 @@ export const formatValue = (amount: number | string, options?: FormatOptions): s
numberOfZeroDecimals = decimals.length numberOfZeroDecimals = decimals.length
} }
} }
let convertedAmount: number | string = new BigNumber(amount) let convertedAmount: number | string = BN(amount)
.dividedBy(10 ** (options?.decimals ?? 0)) .dividedBy(10 ** (options?.decimals ?? 0))
.toNumber() .toNumber()
@ -143,3 +142,15 @@ export const convertPercentage = (percent: number) => {
if (percent !== 0 && percent < 0.01) percentage = 0.01 if (percent !== 0 && percent < 0.01) percentage = 0.01
return Number(formatValue(percentage, { minDecimals: 0, maxDecimals: 0 })) return Number(formatValue(percentage, { minDecimals: 0, maxDecimals: 0 }))
} }
export function magnify(value: number, asset: Asset) {
return value === 0 ? 0 : BN(value).shiftedBy(asset.decimals).toNumber()
}
export function demagnify(amount: number, asset: Asset) {
return amount === 0
? 0
: BN(amount)
.shiftedBy(-1 * asset.decimals)
.toNumber()
}

5
src/utils/helpers.ts Normal file
View File

@ -0,0 +1,5 @@
import BigNumber from 'bignumber.js'
export function BN(n: BigNumber.Value) {
return new BigNumber(n)
}

View File

@ -8,6 +8,7 @@ export function resolvePositionResponse(response: AccountResponse): Account {
deposits: response.deposits, deposits: response.deposits,
debts: response.debts, debts: response.debts,
lends: response.lends, lends: response.lends,
vaults: response.vaults,
} }
} }

201
yarn.lock
View File

@ -1901,6 +1901,11 @@
resolved "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.2.tgz" resolved "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.2.tgz"
integrity sha512-9anpBMM9mEgZN4wr2v8wHJI2/u5TnnggewRN6OlvXTTnuVyoY19X6rOv9XTqKRw6dcGKwZsBi8n0kDE2I5i4VA== integrity sha512-9anpBMM9mEgZN4wr2v8wHJI2/u5TnnggewRN6OlvXTTnuVyoY19X6rOv9XTqKRw6dcGKwZsBi8n0kDE2I5i4VA==
"@graphql-typed-document-node/core@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
"@humanwhocodes/config-array@^0.11.8": "@humanwhocodes/config-array@^0.11.8":
version "0.11.8" version "0.11.8"
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz"
@ -2264,10 +2269,10 @@
big-integer "^1.6.48" big-integer "^1.6.48"
utility-types "^3.10.0" utility-types "^3.10.0"
"@marsprotocol/wallet-connector@^1.5.3": "@marsprotocol/wallet-connector@^1.5.4":
version "1.5.3" version "1.5.4"
resolved "https://registry.yarnpkg.com/@marsprotocol/wallet-connector/-/wallet-connector-1.5.3.tgz#b77c814f7e5b824b9b8babf4f5a0a6879e90ffba" resolved "https://registry.yarnpkg.com/@marsprotocol/wallet-connector/-/wallet-connector-1.5.4.tgz#81d0e213e9e9c4fff78781cd49fd627b75076fe6"
integrity sha512-XBAy+Kn0kVoE+l/jif7Q9Q/7uC3rTVuFaDMmL7ks2p85ImCO0HQFpvZKv901jguDM230vKI+Hs942CszaS21WA== integrity sha512-RkMVyFkSEDrDcb8SnBnOA3wVWT/zaNdzpgbLpO+Zn8vsYGea8uugafSKCeodKNi83/Q2rBEbTUqMJeyLYI9OMQ==
dependencies: dependencies:
"@cosmjs/cosmwasm-stargate" "^0.30.1" "@cosmjs/cosmwasm-stargate" "^0.30.1"
"@cosmjs/encoding" "^0.30.1" "@cosmjs/encoding" "^0.30.1"
@ -2597,26 +2602,26 @@
"@noble/hashes" "~1.2.0" "@noble/hashes" "~1.2.0"
"@scure/base" "~1.1.0" "@scure/base" "~1.1.0"
"@sentry-internal/tracing@7.47.0": "@sentry-internal/tracing@7.48.0":
version "7.47.0" version "7.48.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.47.0.tgz#45e92eb4c8d049d93bd4fab961eaa38a4fb680f3" resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.48.0.tgz#d0c1eac1c046fda5c79d16dc1c918fee3bae3e9d"
integrity sha512-udpHnCzF8DQsWf0gQwd0XFGp6Y8MOiwnl8vGt2ohqZGS3m1+IxoRLXsSkD8qmvN6KKDnwbaAvYnK0z0L+AW95g== integrity sha512-MFAPDTrvCtfSm0/Zbmx7HA0Q5uCfRadOUpN8Y8rP1ndz+329h2kA3mZRCuC+3/aXL11zs2CHUhcAkGjwH2vogg==
dependencies: dependencies:
"@sentry/core" "7.47.0" "@sentry/core" "7.48.0"
"@sentry/types" "7.47.0" "@sentry/types" "7.48.0"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.48.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/browser@7.47.0": "@sentry/browser@7.48.0":
version "7.47.0" version "7.48.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.47.0.tgz#c0d10f348d1fb9336c3ef8fa2f6638f26d4c17a8" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.48.0.tgz#03f39bec6949ff48e343c5862c5d54dfd4a2f9ff"
integrity sha512-L0t07kS/G1UGVZ9fpD6HLuaX8vVBqAGWgu+1uweXthYozu/N7ZAsakjU/Ozu6FSXj1mO3NOJZhOn/goIZLSj5A== integrity sha512-tdx/2nhuiykncmXFlV4Dpp+Hxgt/v31LiyXE79IcM560wc+QmWKtzoW9azBWQ0xt5KOO3ERMib9qPE4/ql1/EQ==
dependencies: dependencies:
"@sentry-internal/tracing" "7.47.0" "@sentry-internal/tracing" "7.48.0"
"@sentry/core" "7.47.0" "@sentry/core" "7.48.0"
"@sentry/replay" "7.47.0" "@sentry/replay" "7.48.0"
"@sentry/types" "7.47.0" "@sentry/types" "7.48.0"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.48.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/cli@^1.74.6": "@sentry/cli@^1.74.6":
@ -2631,88 +2636,88 @@
proxy-from-env "^1.1.0" proxy-from-env "^1.1.0"
which "^2.0.2" which "^2.0.2"
"@sentry/core@7.47.0": "@sentry/core@7.48.0":
version "7.47.0" version "7.48.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.47.0.tgz#6a723d96f64009a9c1b9bc44e259956b7eca0a3f" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.48.0.tgz#1a5ec347ab7212d73a99583c2e64989e34e3263a"
integrity sha512-EFhZhKdMu7wKmWYZwbgTi8FNZ7Fq+HdlXiZWNz51Bqe3pHmfAkdHtAEs0Buo0v623MKA0CA4EjXIazGUM34XTg== integrity sha512-8FYuJTMpyuxRZvlen3gQ3rpOtVInSDmSyXqWEhCLuG/w34AtWoTiW7G516rsAAh6Hy1TP91GooMWbonP3XQNTQ==
dependencies: dependencies:
"@sentry/types" "7.47.0" "@sentry/types" "7.48.0"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.48.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/integrations@7.47.0": "@sentry/integrations@7.48.0":
version "7.47.0" version "7.48.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.47.0.tgz#b952cc910e92e9235f42151f7260471b55b10cdf" resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.48.0.tgz#1f1c3fd0735b3c40944a42fb45e3e0ca40f77d6d"
integrity sha512-PUSeBYI3fCOswn+K+PLjtl2epr8/ceqebWqVcxHclczSY3EOZE+osznDFgZmeVgrHavsgfE4oFVqJeFvDJwCog== integrity sha512-yzbJopVu1UHFXRDv236o5hSEUtqeP45T9uSVbAhKnH5meKWunK7MKvhFvQjhcfvlUVibYrewoVztQP2hrpxgfw==
dependencies: dependencies:
"@sentry/types" "7.47.0" "@sentry/types" "7.48.0"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.48.0"
localforage "^1.8.1" localforage "^1.8.1"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/nextjs@^7.47.0": "@sentry/nextjs@^7.48.0":
version "7.47.0" version "7.48.0"
resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.47.0.tgz#72c0a3ecea87940db3eadeb94f6b47eb8f3ead64" resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.48.0.tgz#e1c606eb31a907cdc00f96baebffdba3560405b5"
integrity sha512-KcvN0l5N819LdX7iFUrZjYTX5ITm8lXmiOSyhiLTZBm68ZZbmX2TMrMMlGCLuc0qBZQolu11u6gVQSfTaZPQ9Q== integrity sha512-SLWkd1ZB27uK21QkUiIBEUgvhaMFMx8V5MO2+IlGluJKUdd06IgYAOsS0kjwQc34Ow6D0qowy8iScmtHebgQew==
dependencies: dependencies:
"@rollup/plugin-commonjs" "24.0.0" "@rollup/plugin-commonjs" "24.0.0"
"@sentry/core" "7.47.0" "@sentry/core" "7.48.0"
"@sentry/integrations" "7.47.0" "@sentry/integrations" "7.48.0"
"@sentry/node" "7.47.0" "@sentry/node" "7.48.0"
"@sentry/react" "7.47.0" "@sentry/react" "7.48.0"
"@sentry/types" "7.47.0" "@sentry/types" "7.48.0"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.48.0"
"@sentry/webpack-plugin" "1.20.0" "@sentry/webpack-plugin" "1.20.0"
chalk "3.0.0" chalk "3.0.0"
rollup "2.78.0" rollup "2.78.0"
stacktrace-parser "^0.1.10" stacktrace-parser "^0.1.10"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/node@7.47.0": "@sentry/node@7.48.0":
version "7.47.0" version "7.48.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.47.0.tgz#2c1a8c4777eaf20232fc16d3aa2f26f3fd04bfd1" resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.48.0.tgz#b2f15502b77796bf7bcaa29f2e9ce1420f7c49d1"
integrity sha512-LTg2r5EV9yh4GLYDF+ViSltR9LLj/pcvk8YhANJcMO3Fp//xh8njcdU0FC2yNthUREawYDzAsVzLyCYJfV0H1A== integrity sha512-DJyyZaVhv/pUzJPof7es6zYDHeWbNqE0T3tQfLCkShdyfR+Ew8In8W/x2s7S8vq0cfRq0rqv1E6B2/HpVdYO7g==
dependencies: dependencies:
"@sentry-internal/tracing" "7.47.0" "@sentry-internal/tracing" "7.48.0"
"@sentry/core" "7.47.0" "@sentry/core" "7.48.0"
"@sentry/types" "7.47.0" "@sentry/types" "7.48.0"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.48.0"
cookie "^0.4.1" cookie "^0.4.1"
https-proxy-agent "^5.0.0" https-proxy-agent "^5.0.0"
lru_map "^0.3.3" lru_map "^0.3.3"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/react@7.47.0": "@sentry/react@7.48.0":
version "7.47.0" version "7.48.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.47.0.tgz#9b0b937465a4e7fc6c3bde90ef9d0be2ef708b78" resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.48.0.tgz#3c160d7dcccc7581b57c430fccfe482c912c3f0d"
integrity sha512-Qy6OnlE8FivKOLo0YE7tkr+G5fLmEOkpPxj179wbY/N8kp/ALkqbVdcOrZW7AL6HCc0lphhj+0SB+tpwoPEsiQ== integrity sha512-E2HF0njufOI/BWktXfIiPNIh0dh7la9uQmDlYiFAK8MnlW4OOjw4rRJV2qkxKQCYdO9WB+T460DVw102Z/MyUA==
dependencies: dependencies:
"@sentry/browser" "7.47.0" "@sentry/browser" "7.48.0"
"@sentry/types" "7.47.0" "@sentry/types" "7.48.0"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.48.0"
hoist-non-react-statics "^3.3.2" hoist-non-react-statics "^3.3.2"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/replay@7.47.0": "@sentry/replay@7.48.0":
version "7.47.0" version "7.48.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.47.0.tgz#d2fc8fd3be2360950497426035d1ba0bd8a97b8f" resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.48.0.tgz#ca8f1543bad4717dcd65739bf1256a1933bba757"
integrity sha512-BFpVZVmwlezZ83y0L43TCTJY142Fxh+z+qZSwTag5HlhmIpBKw/WKg06ajOhrYJbCBkhHmeOvyKkxX0jnc39ZA== integrity sha512-8fRHMGJ0NJeIZi6UucxUTvfDPaBa7+jU1kCTLjCcuH3X/UVz5PtGLMtFSO5U8HP+mUDlPs97MP1uoDvMa4S2Ng==
dependencies: dependencies:
"@sentry/core" "7.47.0" "@sentry/core" "7.48.0"
"@sentry/types" "7.47.0" "@sentry/types" "7.48.0"
"@sentry/utils" "7.47.0" "@sentry/utils" "7.48.0"
"@sentry/types@7.47.0": "@sentry/types@7.48.0":
version "7.47.0" version "7.48.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.47.0.tgz#fd07dbec11a26ae861532a9abe75bd31663ca09b" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.48.0.tgz#57f3c9cf331a5621e82dda04eefcf8c19ee42bc9"
integrity sha512-GxXocplN0j1+uczovHrfkykl9wvkamDtWxlPUQgyGlbLGZn+UH1Y79D4D58COaFWGEZdSNKr62gZAjfEYu9nQA== integrity sha512-kkAszZwQ5/v4n7Yyw/DPNRWx7h724mVNRGZIJa9ggUMvTgMe7UKCZZ5wfQmYiKVlGbwd9pxXAcP8Oq15EbByFQ==
"@sentry/utils@7.47.0": "@sentry/utils@7.48.0":
version "7.47.0" version "7.48.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.47.0.tgz#e62fdede15e45387b40c9fa135feba48f0960826" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.48.0.tgz#2866975ea8899aba35b083dd0558cbbe29ee8de1"
integrity sha512-A89SaOLp6XeZfByeYo2C8Ecye/YAtk/gENuyOUhQEdMulI6mZdjqtHAp7pTMVgkBc/YNARVuoa+kR/IdRrTPkQ== integrity sha512-d977sghkFVMfld0LrEyyY2gYrfayLPdDEpUDT+hg5y79r7zZDCFyHtdB86699E5K89MwDZahW7Erk+a1nk4x5w==
dependencies: dependencies:
"@sentry/types" "7.47.0" "@sentry/types" "7.48.0"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/webpack-plugin@1.20.0": "@sentry/webpack-plugin@1.20.0":
@ -3032,10 +3037,10 @@
"@types/scheduler" "*" "@types/scheduler" "*"
csstype "^3.0.2" csstype "^3.0.2"
"@types/react@18.0.33": "@types/react@18.0.35":
version "18.0.33" version "18.0.35"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.33.tgz#a1575160cb4376787c2f5fe0312302f824baa61e" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.35.tgz#192061cb1044fe01f2d3a94272cd35dd50502741"
integrity sha512-sHxzVxeanvQyQ1lr8NSHaj0kDzcNiGpILEVt69g9S31/7PfMvNCKLKcsHw4lYKjs3cGNJjXSP4mYzX43QlnjNA== integrity sha512-6Laome31HpetaIUGFWl1VQ3mdSImwxtFZ39rh059a1MNnKGqBpC88J6NJ8n/Is3Qx7CefDGLgf/KhN/sYCf7ag==
dependencies: dependencies:
"@types/prop-types" "*" "@types/prop-types" "*"
"@types/scheduler" "*" "@types/scheduler" "*"
@ -4985,11 +4990,6 @@ exenv@^1.2.0:
resolved "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz" resolved "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz"
integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==
extract-files@^9.0.0:
version "9.0.0"
resolved "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz"
integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
@ -5093,15 +5093,6 @@ for-each@^0.3.3:
dependencies: dependencies:
is-callable "^1.1.3" is-callable "^1.1.3"
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@^4.0.0: form-data@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz"
@ -5318,15 +5309,13 @@ grapheme-splitter@^1.0.4:
resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz"
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
graphql-request@^5.2.0: graphql-request@^6.0.0:
version "5.2.0" version "6.0.0"
resolved "https://registry.npmjs.org/graphql-request/-/graphql-request-5.2.0.tgz" resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-6.0.0.tgz#9c8b6a0c341f289e049936d03cc9205300faae1c"
integrity sha512-pLhKIvnMyBERL0dtFI3medKqWOz/RhHdcgbZ+hMMIb32mEPa5MJSzS4AuXxfI4sRAu6JVVk5tvXuGfCWl9JYWQ== integrity sha512-2BmHTuglonjZvmNVw6ZzCfFlW/qkIPds0f+Qdi/Lvjsl3whJg2uvHmSvHnLWhUTEw6zcxPYAHiZoPvSVKOZ7Jw==
dependencies: dependencies:
"@graphql-typed-document-node/core" "^3.1.1" "@graphql-typed-document-node/core" "^3.2.0"
cross-fetch "^3.1.5" cross-fetch "^3.1.5"
extract-files "^9.0.0"
form-data "^3.0.0"
graphql-tag@^2.12.6: graphql-tag@^2.12.6:
version "2.12.6" version "2.12.6"
@ -6546,10 +6535,10 @@ prelude-ls@^1.2.1:
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier-plugin-tailwindcss@^0.2.6: prettier-plugin-tailwindcss@^0.2.7:
version "0.2.6" version "0.2.7"
resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.6.tgz#d57e3d960f1a968bf16d2a5846e94f95d6b8add2" resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.2.7.tgz#2314d728cce9c9699ced41a01253eb48b4218da5"
integrity sha512-F+7XCl9RLF/LPrGdUMHWpsT6TM31JraonAUyE6eBmpqymFvDwyl0ETHsKFHP1NG+sEfv8bmKqnTxEbWQbHPlBA== integrity sha512-jQopIOgjLpX+y8HeD56XZw7onupRTC0cw7eKKUimI7vhjkPF5/1ltW5LyqaPtSyc8HvEpvNZsvvsGFa2qpa59w==
prettier@^2.8.7: prettier@^2.8.7:
version "2.8.7" version "2.8.7"
@ -7351,10 +7340,10 @@ svgo@^3.0.2:
csso "^5.0.5" csso "^5.0.5"
picocolors "^1.0.0" picocolors "^1.0.0"
swr@^2.1.2: swr@^2.1.3:
version "2.1.2" version "2.1.3"
resolved "https://registry.yarnpkg.com/swr/-/swr-2.1.2.tgz#15841cf5bbb8b20f24e2408193f616a41b6734a0" resolved "https://registry.yarnpkg.com/swr/-/swr-2.1.3.tgz#ae3608d4dc19f75121e0b51d1982735fbf02f52e"
integrity sha512-ocfaD2rnYZKqTDplCEX2bH5Z1++n2JSej9oYi7hVfXXWYm+0RP+H6fVrogWB0mtMclv1guk9kEnAzNLygOy9Hw== integrity sha512-g3ApxIM4Fjbd6vvEAlW60hJlKcYxHb+wtehogTygrh6Jsw7wNagv9m4Oj5Gq6zvvZw0tcyhVGL9L0oISvl3sUw==
dependencies: dependencies:
use-sync-external-store "^1.2.0" use-sync-external-store "^1.2.0"