update token page layout

This commit is contained in:
saml33 2024-02-08 11:24:17 +11:00
parent 239b0f99b6
commit a9124d5148
16 changed files with 641 additions and 274 deletions

View File

@ -28,7 +28,6 @@ import DepositWithdrawModal from './modals/DepositWithdrawModal'
import BorrowRepayModal from './modals/BorrowRepayModal' import BorrowRepayModal from './modals/BorrowRepayModal'
import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions' import { WRAPPED_SOL_MINT } from '@project-serum/serum/lib/token-instructions'
import { import {
MANGO_DATA_API_URL,
SHOW_ZERO_BALANCES_KEY, SHOW_ZERO_BALANCES_KEY,
TOKEN_REDUCE_ONLY_OPTIONS, TOKEN_REDUCE_ONLY_OPTIONS,
USDC_MINT, USDC_MINT,
@ -48,9 +47,8 @@ import { useSortableData } from 'hooks/useSortableData'
import TableTokenName from './shared/TableTokenName' import TableTokenName from './shared/TableTokenName'
import CloseBorrowModal from './modals/CloseBorrowModal' import CloseBorrowModal from './modals/CloseBorrowModal'
import { floorToDecimal } from 'utils/numbers' import { floorToDecimal } from 'utils/numbers'
import { useQuery } from '@tanstack/react-query'
import { TotalInterestDataItem } from 'types'
import SheenLoader from './shared/SheenLoader' import SheenLoader from './shared/SheenLoader'
import useAccountInterest from 'hooks/useAccountInterest'
export const handleOpenCloseBorrowModal = (borrowBank: Bank) => { export const handleOpenCloseBorrowModal = (borrowBank: Bank) => {
const group = mangoStore.getState().group const group = mangoStore.getState().group
@ -100,30 +98,6 @@ export const handleCloseBorrowModal = () => {
}) })
} }
export const fetchInterestData = async (mangoAccountPk: string) => {
try {
const response = await fetch(
`${MANGO_DATA_API_URL}/stats/interest-account-total?mango-account=${mangoAccountPk}`,
)
const parsedResponse: Omit<TotalInterestDataItem, 'symbol'>[] | null =
await response.json()
if (parsedResponse) {
const entries: [string, Omit<TotalInterestDataItem, 'symbol'>][] =
Object.entries(parsedResponse).sort((a, b) => b[0].localeCompare(a[0]))
const stats: TotalInterestDataItem[] = entries
.map(([key, value]) => {
return { ...value, symbol: key }
})
.filter((x) => x)
return stats
} else return []
} catch (e) {
console.log('Failed to fetch account funding', e)
return []
}
}
type TableData = { type TableData = {
bank: Bank bank: Bank
balance: number balance: number
@ -159,17 +133,7 @@ const TokenList = () => {
const { const {
data: totalInterestData, data: totalInterestData,
isInitialLoading: loadingTotalInterestData, isInitialLoading: loadingTotalInterestData,
} = useQuery( } = useAccountInterest()
['account-interest-data', mangoAccountAddress],
() => fetchInterestData(mangoAccountAddress),
{
cacheTime: 1000 * 60 * 10,
staleTime: 1000 * 60,
retry: 3,
refetchOnWindowFocus: false,
enabled: !!mangoAccountAddress,
},
)
const formattedTableData = useCallback( const formattedTableData = useCallback(
(banks: BankWithBalance[]) => { (banks: BankWithBalance[]) => {

View File

@ -1,112 +1,167 @@
import { Bank } from '@blockworks-foundation/mango-v4' import { Bank } from '@blockworks-foundation/mango-v4'
import BorrowRepayModal from '@components/modals/BorrowRepayModal'
import DepositWithdrawModal from '@components/modals/DepositWithdrawModal' import DepositWithdrawModal from '@components/modals/DepositWithdrawModal'
import Button from '@components/shared/Button' import Button from '@components/shared/Button'
import FormatNumericValue from '@components/shared/FormatNumericValue' import FormatNumericValue from '@components/shared/FormatNumericValue'
import Tooltip from '@components/shared/Tooltip'
import { ArrowDownTrayIcon, ArrowUpTrayIcon } from '@heroicons/react/20/solid'
import mangoStore from '@store/mangoStore' import mangoStore from '@store/mangoStore'
import useAccountInterest from 'hooks/useAccountInterest'
import useHealthContributions from 'hooks/useHealthContributions'
import useMangoAccount from 'hooks/useMangoAccount' import useMangoAccount from 'hooks/useMangoAccount'
import useMangoGroup from 'hooks/useMangoGroup'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { useRouter } from 'next/router'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
const ActionPanel = ({ bank }: { bank: Bank }) => { const ActionPanel = ({ bank }: { bank: Bank }) => {
const { t } = useTranslation('common') const { t } = useTranslation(['common', 'trade'])
const { group } = useMangoGroup()
const { mangoAccount } = useMangoAccount() const { mangoAccount } = useMangoAccount()
const router = useRouter() const spotBalances = mangoStore((s) => s.mangoAccount.spotBalances)
const [showDepositModal, setShowDepositModal] = useState(false) const { initContributions } = useHealthContributions()
const [showBorrowModal, setShowBorrowModal] = useState(false) const [showDepositModal, setShowDepositModal] = useState<
const spotMarkets = mangoStore((s) => s.serumMarkets) 'deposit' | 'withdraw' | ''
>('')
const { data: totalInterestData } = useAccountInterest()
const serumMarkets = useMemo(() => { const [depositRate, borrowRate] = useMemo(() => {
if (group) { const depositRate = bank.getDepositRateUi()
return Array.from(group.serum3MarketsMapByExternal.values()) const borrowRate = bank.getBorrowRateUi()
} return [depositRate, borrowRate]
return [] }, [bank])
}, [group])
const handleTrade = () => { const collateralValue =
const markets = spotMarkets.filter( initContributions.find((val) => val.asset === bank.name)?.contribution || 0
(m) => m.baseTokenIndex === bank?.tokenIndex, const inOrders = spotBalances[bank.mint.toString()]?.inOrders || 0
) const unsettled = spotBalances[bank.mint.toString()]?.unsettled || 0
if (markets) {
if (markets.length === 1) { const symbol = bank.name === 'MSOL' ? 'mSOL' : bank.name
router.push(`/trade?name=${markets[0].name}`) const hasInterestEarned = totalInterestData?.find(
} (d) =>
if (markets.length > 1) { d.symbol.toLowerCase() === symbol.toLowerCase() ||
const market = markets.find((mkt) => !mkt.reduceOnly) (symbol === 'ETH (Portal)' && d.symbol === 'ETH'),
if (market) { )
router.push(`/trade?name=${market.name}`)
} const interestAmount = hasInterestEarned
} ? hasInterestEarned.borrow_interest * -1 +
} hasInterestEarned.deposit_interest
} : 0
return ( return (
<> <>
<div className="w-full rounded-md bg-th-bkg-2 p-4 md:w-[343px]"> <div className="h-full w-full bg-th-bkg-2 p-4 md:p-6">
<div className="mb-4 flex justify-between"> <h2 className="mb-4 text-lg">Your {bank?.name}</h2>
<p> <div className="border-b border-th-bkg-4">
{bank.name} {t('balance')}: <div className="flex justify-between border-t border-th-bkg-4 py-3">
</p> <p>
<p className="font-mono text-th-fgd-2"> {bank.name} {t('balance')}
{mangoAccount ? ( </p>
<FormatNumericValue <p className="font-mono text-th-fgd-2">
value={mangoAccount.getTokenBalanceUi(bank)} {mangoAccount ? (
decimals={bank.mintDecimals} <FormatNumericValue
/> value={mangoAccount.getTokenBalanceUi(bank)}
) : ( decimals={bank.mintDecimals}
0 />
)} ) : (
</p> 0
)}
</p>
</div>
<div className="flex justify-between border-t border-th-bkg-4 py-3">
<p>{t('collateral-value')}</p>
<p className="font-mono text-th-fgd-2">
$
{mangoAccount ? (
<FormatNumericValue value={collateralValue} decimals={2} />
) : (
'0.00'
)}
</p>
</div>
<div className="flex justify-between border-t border-th-bkg-4 py-3">
<p>{t('trade:in-orders')}</p>
<p className="font-mono text-th-fgd-2">
{inOrders ? (
<FormatNumericValue
value={inOrders}
decimals={bank.mintDecimals}
/>
) : (
0
)}
</p>
</div>
<div className="flex justify-between border-t border-th-bkg-4 py-3">
<p>{t('trade:unsettled')}</p>
<p className="font-mono text-th-fgd-2">
{unsettled ? (
<FormatNumericValue
value={unsettled}
decimals={bank.mintDecimals}
/>
) : (
0
)}
</p>
</div>
<div className="flex justify-between border-t border-th-bkg-4 py-3">
<p>{t('interest-earned')}</p>
<p className="font-mono text-th-fgd-2">
{interestAmount ? (
<FormatNumericValue
value={interestAmount}
decimals={bank.mintDecimals}
/>
) : (
0
)}
</p>
</div>
<div className="flex justify-between border-t border-th-bkg-4 py-3">
<p>{t('rates')}</p>
<div className="flex justify-end space-x-1.5">
<Tooltip content={t('deposit-rate')}>
<p className="cursor-help font-mono text-th-up">
<FormatNumericValue value={depositRate} decimals={2} />%
</p>
</Tooltip>
<span className="text-th-fgd-4">|</span>
<Tooltip content={t('borrow-rate')}>
<p className="cursor-help font-mono text-th-down">
<FormatNumericValue value={borrowRate} decimals={2} />%
</p>
</Tooltip>
</div>
</div>
</div> </div>
<div className="flex space-x-2"> <div className="flex space-x-4 pt-8">
<Button <Button
className="flex-1" className="flex-1"
size="small" secondary
disabled={!mangoAccount} disabled={!mangoAccount}
onClick={() => setShowDepositModal(true)} onClick={() => setShowDepositModal('deposit')}
> >
{t('deposit')} <div className="flex items-center space-x-2">
<ArrowDownTrayIcon className="h-5 w-5" />
<span>{t('deposit')}</span>
</div>
</Button> </Button>
<Button <Button
className="flex-1" className="flex-1"
size="small"
secondary secondary
disabled={!mangoAccount} disabled={!mangoAccount}
onClick={() => setShowBorrowModal(true)} onClick={() => setShowDepositModal('withdraw')}
> >
{t('borrow')} <div className="flex items-center space-x-2">
</Button> <ArrowUpTrayIcon className="h-5 w-5" />
<Button <span>{t('withdraw')}</span>
className="flex-1" </div>
size="small"
secondary
disabled={
!mangoAccount ||
!serumMarkets.find((m) => m.baseTokenIndex === bank?.tokenIndex)
}
onClick={handleTrade}
>
{t('trade')}
</Button> </Button>
</div> </div>
</div> </div>
{showDepositModal ? ( {showDepositModal ? (
<DepositWithdrawModal <DepositWithdrawModal
action="deposit" action={showDepositModal}
isOpen={showDepositModal} isOpen={!!showDepositModal}
onClose={() => setShowDepositModal(false)} onClose={() => setShowDepositModal('')}
token={bank!.name} token={bank?.name}
/>
) : null}
{showBorrowModal ? (
<BorrowRepayModal
action="borrow"
isOpen={showBorrowModal}
onClose={() => setShowBorrowModal(false)}
token={bank!.name}
/> />
) : null} ) : null}
</> </>

View File

@ -61,8 +61,12 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
const [showBorrowsRelativeChange, setShowBorrowsRelativeChange] = const [showBorrowsRelativeChange, setShowBorrowsRelativeChange] =
useState(true) useState(true)
const [showBorrowsNotional, setShowBorrowsNotional] = useState(false) const [showBorrowsNotional, setShowBorrowsNotional] = useState(false)
const [activeDepositsTab, setActiveDepositsTab] = useState('token:deposits') const [activeDepositsTab, setActiveDepositsTab] = useState(
const [activeBorrowsTab, setActiveBorrowsTab] = useState('token:borrows') 'token:total-deposits',
)
const [activeBorrowsTab, setActiveBorrowsTab] = useState(
'token:total-borrows',
)
const [depositDaysToShow, setDepositDaysToShow] = useState('30') const [depositDaysToShow, setDepositDaysToShow] = useState('30')
const [borrowDaysToShow, setBorrowDaysToShow] = useState('30') const [borrowDaysToShow, setBorrowDaysToShow] = useState('30')
const [depositRateDaysToShow, setDepositRateDaysToShow] = useState('30') const [depositRateDaysToShow, setDepositRateDaysToShow] = useState('30')
@ -163,12 +167,12 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
onChange={(v) => setActiveDepositsTab(v)} onChange={(v) => setActiveDepositsTab(v)}
showBorders showBorders
values={[ values={[
['token:deposits', 0], ['token:total-deposits', 0],
['token:deposit-rates', 0], ['token:deposit-rates', 0],
]} ]}
/> />
<div className="border-t border-th-bkg-3"> <div className="border-t border-th-bkg-3">
{activeDepositsTab === 'token:deposits' ? ( {activeDepositsTab === 'token:total-deposits' ? (
<> <>
<div className="px-4 pt-4 md:px-6 lg:pt-6"> <div className="px-4 pt-4 md:px-6 lg:pt-6">
<DetailedAreaOrBarChart <DetailedAreaOrBarChart
@ -241,12 +245,12 @@ const ChartTabs = ({ bank }: { bank: Bank }) => {
onChange={(v) => setActiveBorrowsTab(v)} onChange={(v) => setActiveBorrowsTab(v)}
showBorders showBorders
values={[ values={[
['token:borrows', 0], ['token:total-borrows', 0],
['token:borrow-rates', 0], ['token:borrow-rates', 0],
]} ]}
/> />
<div className="border-t border-th-bkg-3"> <div className="border-t border-th-bkg-3">
{activeBorrowsTab === 'token:borrows' ? ( {activeBorrowsTab === 'token:total-borrows' ? (
<> <>
<div className="px-4 pt-4 md:px-6 lg:pt-6"> <div className="px-4 pt-4 md:px-6 lg:pt-6">
<DetailedAreaOrBarChart <DetailedAreaOrBarChart

View File

@ -2,17 +2,11 @@ import { Bank } from '@blockworks-foundation/mango-v4'
import Change from '@components/shared/Change' import Change from '@components/shared/Change'
import FormatNumericValue from '@components/shared/FormatNumericValue' import FormatNumericValue from '@components/shared/FormatNumericValue'
import { ArrowSmallUpIcon } from '@heroicons/react/20/solid' import { ArrowSmallUpIcon } from '@heroicons/react/20/solid'
import { useQuery } from '@tanstack/react-query'
import { makeApiRequest } from 'apis/birdeye/helpers'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import parse from 'html-react-parser' import parse from 'html-react-parser'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { DAILY_SECONDS } from 'utils/constants'
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
import { countLeadingZeros, formatCurrencyValue } from 'utils/numbers'
import { BirdeyePriceResponse } from 'types'
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
const DEFAULT_COINGECKO_VALUES = { const DEFAULT_COINGECKO_VALUES = {
@ -33,27 +27,6 @@ const DEFAULT_COINGECKO_VALUES = {
total_volume: 0, total_volume: 0,
} }
interface BirdeyeResponse {
data: { items: BirdeyePriceResponse[] }
success: boolean
}
const fetchBirdeyePrices = async (
daysToShow: string,
mint: string,
): Promise<BirdeyePriceResponse[] | []> => {
const interval = daysToShow === '1' ? '30m' : daysToShow === '7' ? '1H' : '4H'
const queryEnd = Math.floor(Date.now() / 1000)
const queryStart = queryEnd - parseInt(daysToShow) * DAILY_SECONDS
const query = `defi/history_price?address=${mint}&address_type=token&type=${interval}&time_from=${queryStart}&time_to=${queryEnd}`
const response: BirdeyeResponse = await makeApiRequest(query)
if (response.success && response?.data?.items) {
return response.data.items
}
return []
}
const CoingeckoStats = ({ const CoingeckoStats = ({
bank, bank,
coingeckoData, coingeckoData,
@ -65,32 +38,6 @@ const CoingeckoStats = ({
}) => { }) => {
const { t } = useTranslation(['common', 'token']) const { t } = useTranslation(['common', 'token'])
const [showFullDesc, setShowFullDesc] = useState(false) const [showFullDesc, setShowFullDesc] = useState(false)
const [daysToShow, setDaysToShow] = useState<string>('1')
const { data: birdeyePrices, isLoading: loadingBirdeyePrices } = useQuery(
['birdeye-token-prices', daysToShow, bank.mint],
() => fetchBirdeyePrices(daysToShow, bank.mint.toString()),
{
cacheTime: 1000 * 60 * 15,
staleTime: 1000 * 60 * 10,
retry: 3,
enabled: !!bank,
refetchOnWindowFocus: false,
},
)
const chartData = useMemo(() => {
if (!birdeyePrices || !birdeyePrices.length) return []
return birdeyePrices.map((item) => {
const decimals = countLeadingZeros(item.value) + 3
const floatPrice = parseFloat(item.value.toString())
const roundedPrice = +floatPrice.toFixed(decimals)
return {
unixTime: item.unixTime * 1000,
value: roundedPrice,
}
})
}, [birdeyePrices])
const { const {
ath, ath,
@ -142,35 +89,8 @@ const CoingeckoStats = ({
</div> </div>
</div> </div>
) : null} ) : null}
<div className="p-6 pb-4">
<DetailedAreaOrBarChart
changeAsPercent
data={chartData.concat([
{
unixTime: Date.now(),
value: parseFloat(
bank.uiPrice.toFixed(countLeadingZeros(bank.uiPrice) + 3),
),
},
])}
daysToShow={daysToShow}
setDaysToShow={setDaysToShow}
loading={loadingBirdeyePrices}
heightClass="h-64"
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) =>
x < 0.00001 ? x.toExponential() : formatCurrencyValue(x)
}
title={`${bank.name} Price Chart`}
xKey="unixTime"
yKey="value"
yDecimals={countLeadingZeros(bank.uiPrice) + 3}
domain={['dataMin', 'dataMax']}
/>
</div>
<div className="grid grid-cols-1 border-b border-th-bkg-3 md:grid-cols-2"> <div className="grid grid-cols-1 border-b border-th-bkg-3 md:grid-cols-2">
<div className="col-span-1 border-y border-th-bkg-3 px-6 py-4 md:col-span-2"> <div className="col-span-1 border-b border-th-bkg-3 px-6 py-4 md:col-span-2">
<h2 className="text-base">{bank.name} Stats</h2> <h2 className="text-base">{bank.name} Stats</h2>
</div> </div>
<div className="col-span-1 px-6 py-4 md:border-r md:border-th-bkg-3"> <div className="col-span-1 px-6 py-4 md:border-r md:border-th-bkg-3">

View File

@ -0,0 +1,95 @@
import { Bank } from '@blockworks-foundation/mango-v4'
import { useQuery } from '@tanstack/react-query'
import { makeApiRequest } from 'apis/birdeye/helpers'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { useMemo, useState } from 'react'
import { DAILY_SECONDS } from 'utils/constants'
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
import { countLeadingZeros, formatCurrencyValue } from 'utils/numbers'
import { BirdeyePriceResponse } from 'types'
dayjs.extend(relativeTime)
interface BirdeyeResponse {
data: { items: BirdeyePriceResponse[] }
success: boolean
}
const fetchBirdeyePrices = async (
daysToShow: string,
mint: string,
): Promise<BirdeyePriceResponse[] | []> => {
const interval = daysToShow === '1' ? '30m' : daysToShow === '7' ? '1H' : '4H'
const queryEnd = Math.floor(Date.now() / 1000)
const queryStart = queryEnd - parseInt(daysToShow) * DAILY_SECONDS
const query = `defi/history_price?address=${mint}&address_type=token&type=${interval}&time_from=${queryStart}&time_to=${queryEnd}`
const response: BirdeyeResponse = await makeApiRequest(query)
if (response.success && response?.data?.items) {
return response.data.items
}
return []
}
const PriceChart = ({ bank }: { bank: Bank }) => {
const [daysToShow, setDaysToShow] = useState<string>('1')
const { data: birdeyePrices, isLoading: loadingBirdeyePrices } = useQuery(
['birdeye-token-prices', daysToShow, bank.mint],
() => fetchBirdeyePrices(daysToShow, bank.mint.toString()),
{
cacheTime: 1000 * 60 * 15,
staleTime: 1000 * 60 * 10,
retry: 3,
enabled: !!bank,
refetchOnWindowFocus: false,
},
)
const chartData = useMemo(() => {
if (!birdeyePrices || !birdeyePrices.length) return []
return birdeyePrices.map((item) => {
const decimals = countLeadingZeros(item.value) + 3
const floatPrice = parseFloat(item.value.toString())
const roundedPrice = +floatPrice.toFixed(decimals)
return {
unixTime: item.unixTime * 1000,
value: roundedPrice,
}
})
}, [birdeyePrices])
return (
<>
<div className="p-6 pb-4">
<DetailedAreaOrBarChart
changeAsPercent
data={chartData.concat([
{
unixTime: Date.now(),
value: parseFloat(
bank.uiPrice.toFixed(countLeadingZeros(bank.uiPrice) + 3),
),
},
])}
daysToShow={daysToShow}
setDaysToShow={setDaysToShow}
loading={loadingBirdeyePrices}
heightClass="h-80"
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) =>
x < 0.00001 ? x.toExponential() : formatCurrencyValue(x)
}
title=""
xKey="unixTime"
yKey="value"
yDecimals={countLeadingZeros(bank.uiPrice) + 3}
domain={['dataMin', 'dataMax']}
/>
</div>
</>
)
}
export default PriceChart

View File

@ -1,28 +1,29 @@
import Change from '@components/shared/Change'
import DailyRange from '@components/shared/DailyRange' import DailyRange from '@components/shared/DailyRange'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import FlipNumbers from 'react-flip-numbers'
import { formatCurrencyValue } from 'utils/numbers'
import Link from 'next/link' import Link from 'next/link'
import SheenLoader from '@components/shared/SheenLoader' import SheenLoader from '@components/shared/SheenLoader'
import useMangoGroup from 'hooks/useMangoGroup' import useMangoGroup from 'hooks/useMangoGroup'
import useJupiterMints from 'hooks/useJupiterMints' import useJupiterMints from 'hooks/useJupiterMints'
import useLocalStorageState from 'hooks/useLocalStorageState'
import { ANIMATION_SETTINGS_KEY } from 'utils/constants'
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
import ActionPanel from './ActionPanel' import ActionPanel from './ActionPanel'
import ChartTabs from './ChartTabs' import ChartTabs from './ChartTabs'
import CoingeckoStats from './CoingeckoStats'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import TopTokenAccounts from './TopTokenAccounts' import TopTokenAccounts from './TopTokenAccounts'
import TokenParams from './TokenParams' import TokenParams from './TokenParams'
import { formatTokenSymbol } from 'utils/tokens' import { formatTokenSymbol } from 'utils/tokens'
import TokenLogo from '@components/shared/TokenLogo' import TokenLogo from '@components/shared/TokenLogo'
import { ArrowLeftIcon } from '@heroicons/react/20/solid' import {
ArrowLeftIcon,
ArrowTopRightOnSquareIcon,
ArrowTrendingUpIcon,
ArrowsRightLeftIcon,
} from '@heroicons/react/20/solid'
import RateCurveChart from './RateCurveChart' import RateCurveChart from './RateCurveChart'
import PriceChart from './PriceChart'
import Button from '@components/shared/Button'
import mangoStore from '@store/mangoStore'
import { fetchCMSTokenPage } from 'utils/contentful'
const DEFAULT_COINGECKO_VALUES = { const DEFAULT_COINGECKO_VALUES = {
ath: 0, ath: 0,
@ -65,15 +66,15 @@ const fetchTokenInfo = async (tokenId: string | undefined) => {
const TokenPage = () => { const TokenPage = () => {
const { t } = useTranslation(['common', 'token']) const { t } = useTranslation(['common', 'token'])
// const [cmsTokenData, setCmsTokenData] = useState<TokenPage[] | undefined>(
// undefined,
// )
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const router = useRouter() const router = useRouter()
const { token } = router.query const { token } = router.query
const { group } = useMangoGroup() const { group } = useMangoGroup()
const { mangoTokens } = useJupiterMints() const { mangoTokens } = useJupiterMints()
const [animationSettings] = useLocalStorageState( const spotMarkets = mangoStore((s) => s.serumMarkets)
ANIMATION_SETTINGS_KEY,
INITIAL_ANIMATION_SETTINGS,
)
const bankName = useMemo(() => { const bankName = useMemo(() => {
if (!token) return if (!token) return
@ -84,6 +85,16 @@ const TokenPage = () => {
: token.toString() : token.toString()
}, [token]) }, [token])
// useEffect(() => {
// if (bankName) {
// const fetchCmsData = async () => {
// const tokenData = await fetchCMSTokenPage(bankName)
// setCmsTokenData(tokenData)
// }
// fetchCmsData()
// }
// }, [bankName])
const bank = useMemo(() => { const bank = useMemo(() => {
if (group && bankName) { if (group && bankName) {
const bank = group.banksMapByName.get(bankName) const bank = group.banksMapByName.get(bankName)
@ -102,23 +113,64 @@ const TokenPage = () => {
} }
}, [bank, mangoTokens]) }, [bank, mangoTokens])
const { data: coingeckoTokenInfo, isLoading: loadingCoingeckoInfo } = const { data: coingeckoTokenInfo } = useQuery<CoingeckoDataType, Error>(
useQuery<CoingeckoDataType, Error>( ['coingecko-token-info', coingeckoId],
['coingecko-token-info', coingeckoId], () => fetchTokenInfo(coingeckoId),
() => fetchTokenInfo(coingeckoId), {
{ cacheTime: 1000 * 60 * 15,
cacheTime: 1000 * 60 * 15, staleTime: 1000 * 60 * 5,
staleTime: 1000 * 60 * 5, retry: 3,
retry: 3, refetchOnWindowFocus: false,
refetchOnWindowFocus: false, enabled: !!coingeckoId,
enabled: !!coingeckoId, },
}, )
)
const { high_24h, low_24h, price_change_percentage_24h } = const { data: cmsTokenData } = useQuery(
coingeckoTokenInfo?.market_data ['cms-token-data', bankName],
? coingeckoTokenInfo.market_data () => fetchCMSTokenPage(bankName),
: DEFAULT_COINGECKO_VALUES {
cacheTime: 1000 * 60 * 15,
staleTime: 1000 * 60 * 5,
retry: 3,
refetchOnWindowFocus: false,
enabled: !!bankName,
},
)
const { high_24h, low_24h } = coingeckoTokenInfo?.market_data
? coingeckoTokenInfo.market_data
: DEFAULT_COINGECKO_VALUES
const formatCoingeckoName = (name: string) => {
if (name === 'Wrapped Solana') return 'Solana'
if (name.includes('Wormhole')) return name.replace('Wormhole', 'Portal')
return name
}
const handleTrade = () => {
const markets = spotMarkets.filter(
(m) => m.baseTokenIndex === bank?.tokenIndex,
)
if (markets) {
if (markets.length === 1) {
router.push(`/trade?name=${markets[0].name}`)
}
if (markets.length > 1) {
const market = markets.find((mkt) => !mkt.reduceOnly)
if (market) {
router.push(`/trade?name=${market.name}`)
}
}
}
}
const handleSwap = () => {
if (bank?.name === 'USDC') {
router.push(`/swap?in=USDC&out=SOL`)
} else {
router.push(`/swap?in=USDC&out=${bank?.name}`)
}
}
return ( return (
<> <>
@ -139,55 +191,69 @@ const TokenPage = () => {
</div> </div>
{bank && bankName ? ( {bank && bankName ? (
<> <>
<div className="flex flex-col border-b border-th-bkg-3 px-6 py-5 md:flex-row md:items-center md:justify-between"> <div className="flex flex-col border-b border-th-bkg-3 px-6 pb-4 pt-6 md:flex-row md:items-center md:justify-between">
<div className="mb-4 md:mb-1"> <div className="mb-4 flex flex-col md:mb-1 md:flex-row md:items-center md:space-x-4">
<div className="mb-1.5 flex items-center space-x-2"> <div className="mb-2 w-12 shrink-0 md:mb-0">
<TokenLogo bank={bank} /> <TokenLogo bank={bank} size={48} />
{coingeckoTokenInfo ? (
<h1 className="text-base font-normal">
{coingeckoTokenInfo.name}
</h1>
) : (
<h1 className="text-base font-normal">{bank.name}</h1>
)}
</div> </div>
<div className="flex flex-wrap items-end font-display text-4xl text-th-fgd-1 sm:text-5xl"> <div>
<div className="mb-0.5 mr-3 sm:mb-2"> <div className="flex flex-wrap items-end">
{animationSettings['number-scroll'] ? ( {coingeckoTokenInfo?.name ? (
<FlipNumbers <h1 className="mb-1.5 mr-3">
height={48} {formatCoingeckoName(coingeckoTokenInfo.name)}
width={35} </h1>
play
delay={0.05}
duration={1}
numbers={formatCurrencyValue(bank.uiPrice)}
/>
) : ( ) : (
<FormatNumericValue value={bank.uiPrice} isUsd /> <h1 className="mb-1.5 mr-3">{bank.name}</h1>
)} )}
{cmsTokenData?.length ? (
<a
className="mb-2 flex cursor-pointer items-center text-th-fgd-3 md:hover:text-th-fgd-2"
href={`https://mango.markets/explore/tokens/${cmsTokenData[0]?.slug}`}
rel="noopener noreferrer"
target="_blank"
>
<span>What is {bank?.name}?</span>
<ArrowTopRightOnSquareIcon className="ml-1 h-4 w-4" />
</a>
) : null}
</div> </div>
{coingeckoTokenInfo?.market_data ? ( {high_24h.usd && low_24h.usd ? (
<div className="mb-2"> <DailyRange
<Change change={price_change_percentage_24h} suffix="%" /> high={high_24h.usd}
</div> low={low_24h.usd}
price={bank.uiPrice}
/>
) : null} ) : null}
</div> </div>
{high_24h.usd && low_24h.usd ? (
<DailyRange
high={high_24h.usd}
low={low_24h.usd}
price={bank.uiPrice}
/>
) : null}
</div> </div>
<ActionPanel bank={bank} /> <div className="flex space-x-4 pt-4 sm:pt-0">
<Button
className="flex w-full items-center justify-center sm:w-auto"
onClick={handleSwap}
size="large"
>
<ArrowsRightLeftIcon className="mr-2 h-5 w-5" />
<span>{t('swap')}</span>
</Button>
<Button
className="flex w-full items-center justify-center sm:w-auto"
onClick={handleTrade}
size="large"
>
<ArrowTrendingUpIcon className="mr-2 h-5 w-5" />
<span>{t('trade')}</span>
</Button>
</div>
</div> </div>
<ChartTabs bank={bank} /> <div className="grid grid-cols-12 border-b border-th-bkg-3">
<div className="border-y border-th-bkg-3 px-6 pb-2 pt-6"> <div className="col-span-12 lg:col-span-7 xl:col-span-8">
<RateCurveChart bank={bank} /> <PriceChart bank={bank} />
</div>
<div className="col-span-12 lg:col-span-5 xl:col-span-4">
<ActionPanel bank={bank} />
</div>
</div> </div>
<TopTokenAccounts bank={bank} /> {/* {coingeckoTokenInfo?.market_data ? (
{coingeckoTokenInfo?.market_data ? (
<CoingeckoStats <CoingeckoStats
bank={bank} bank={bank}
coingeckoData={coingeckoTokenInfo.market_data} coingeckoData={coingeckoTokenInfo.market_data}
@ -203,7 +269,15 @@ const TokenPage = () => {
<span className="mb-0.5 text-2xl">🦎</span> <span className="mb-0.5 text-2xl">🦎</span>
<p>No CoinGecko data...</p> <p>No CoinGecko data...</p>
</div> </div>
)} )} */}
{/* <div className="px-4 pb-4 pt-6 md:px-6">
<h2>{bank?.name} on Mango</h2>
</div> */}
<ChartTabs bank={bank} />
<div className="border-y border-th-bkg-3 px-6 pb-2 pt-6">
<RateCurveChart bank={bank} />
</div>
<TopTokenAccounts bank={bank} />
<TokenParams bank={bank} /> <TokenParams bank={bank} />
</> </>
) : loading ? ( ) : loading ? (

View File

@ -0,0 +1,44 @@
import { useQuery } from '@tanstack/react-query'
import { MANGO_DATA_API_URL } from 'utils/constants'
import useMangoAccount from './useMangoAccount'
import { TotalInterestDataItem } from 'types'
const fetchInterestData = async (mangoAccountPk: string) => {
try {
const response = await fetch(
`${MANGO_DATA_API_URL}/stats/interest-account-total?mango-account=${mangoAccountPk}`,
)
const parsedResponse: Omit<TotalInterestDataItem, 'symbol'>[] | null =
await response.json()
if (parsedResponse) {
const entries: [string, Omit<TotalInterestDataItem, 'symbol'>][] =
Object.entries(parsedResponse).sort((a, b) => b[0].localeCompare(a[0]))
const stats: TotalInterestDataItem[] = entries
.map(([key, value]) => {
return { ...value, symbol: key }
})
.filter((x) => x)
return stats
} else return []
} catch (e) {
console.log('Failed to fetch account funding', e)
return []
}
}
export default function useAccountInterest() {
const { mangoAccountAddress } = useMangoAccount()
const { data, isInitialLoading } = useQuery(
['account-interest-data', mangoAccountAddress],
() => fetchInterestData(mangoAccountAddress),
{
cacheTime: 1000 * 60 * 10,
staleTime: 1000 * 60,
retry: 3,
refetchOnWindowFocus: false,
enabled: !!mangoAccountAddress,
},
)
return { data, isInitialLoading }
}

View File

@ -50,6 +50,7 @@
"big.js": "6.2.1", "big.js": "6.2.1",
"bignumber.js": "9.1.2", "bignumber.js": "9.1.2",
"clsx": "1.2.1", "clsx": "1.2.1",
"contentful": "10.6.21",
"csv-stringify": "6.3.2", "csv-stringify": "6.3.2",
"d3-interpolate": "3.0.1", "d3-interpolate": "3.0.1",
"date-fns": "2.29.3", "date-fns": "2.29.3",

View File

@ -49,6 +49,8 @@
"tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.", "tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.",
"top-borrowers": "Top {{symbol}} Borrowers", "top-borrowers": "Top {{symbol}} Borrowers",
"top-depositors": "Top {{symbol}} Depositors", "top-depositors": "Top {{symbol}} Depositors",
"total-borrows": "Total Borrows",
"total-deposits": "Total Deposits",
"total-supply": "Total Supply", "total-supply": "Total Supply",
"total-value": "Total Value", "total-value": "Total Value",
"volume": "24h Volume" "volume": "24h Volume"

View File

@ -49,6 +49,8 @@
"tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.", "tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.",
"top-borrowers": "Top {{symbol}} Borrowers", "top-borrowers": "Top {{symbol}} Borrowers",
"top-depositors": "Top {{symbol}} Depositors", "top-depositors": "Top {{symbol}} Depositors",
"total-borrows": "Total Borrows",
"total-deposits": "Total Deposits",
"total-supply": "Total Supply", "total-supply": "Total Supply",
"total-value": "Total Value", "total-value": "Total Value",
"volume": "24h Volume" "volume": "24h Volume"

View File

@ -49,6 +49,8 @@
"tooltip-token-fees-collected": "Estas taxas são acumuladas em todos os tokens nativos listados no Mango. Os valores neste gráfico são derivados do valor de mercado atual.", "tooltip-token-fees-collected": "Estas taxas são acumuladas em todos os tokens nativos listados no Mango. Os valores neste gráfico são derivados do valor de mercado atual.",
"top-borrowers": "Principais {{symbol}} Devedores", "top-borrowers": "Principais {{symbol}} Devedores",
"top-depositors": "Principais {{symbol}} Depositantes", "top-depositors": "Principais {{symbol}} Depositantes",
"total-borrows": "Total Borrows",
"total-deposits": "Total Deposits",
"total-supply": "Fornecimento Total", "total-supply": "Fornecimento Total",
"total-value": "Valor Total", "total-value": "Valor Total",
"volume": "Volume de 24h" "volume": "Volume de 24h"

View File

@ -49,6 +49,8 @@
"tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.", "tooltip-token-fees-collected": "These fees accrue in every native token listed on Mango. The values in this chart are derived from the current market value.",
"top-borrowers": "Top {{symbol}} Borrowers", "top-borrowers": "Top {{symbol}} Borrowers",
"top-depositors": "Top {{symbol}} Depositors", "top-depositors": "Top {{symbol}} Depositors",
"total-borrows": "Total Borrows",
"total-deposits": "Total Deposits",
"total-supply": "Total Supply", "total-supply": "Total Supply",
"total-value": "Total Value", "total-value": "Total Value",
"volume": "24h Volume" "volume": "24h Volume"

View File

@ -49,6 +49,8 @@
"tooltip-token-fees-collected": "这些费用在 Mango 上买卖的所有币种中都会累积。此图表中的值来自当前市场价值。", "tooltip-token-fees-collected": "这些费用在 Mango 上买卖的所有币种中都会累积。此图表中的值来自当前市场价值。",
"top-borrowers": "顶级{{symbol}}借贷者", "top-borrowers": "顶级{{symbol}}借贷者",
"top-depositors": "顶级{{symbol}}存款者", "top-depositors": "顶级{{symbol}}存款者",
"total-borrows": "Total Borrows",
"total-deposits": "Total Deposits",
"total-supply": "总供应量", "total-supply": "总供应量",
"total-value": "全市值", "total-value": "全市值",
"volume": "24小时交易量" "volume": "24小时交易量"

View File

@ -49,6 +49,8 @@
"tooltip-token-fees-collected": "這些費用在 Mango 上買賣的所有幣種中都會累積。此圖表中的值來自當前市場價值。", "tooltip-token-fees-collected": "這些費用在 Mango 上買賣的所有幣種中都會累積。此圖表中的值來自當前市場價值。",
"top-borrowers": "頂級{{symbol}}借貸者", "top-borrowers": "頂級{{symbol}}借貸者",
"top-depositors": "頂級{{symbol}}存款者", "top-depositors": "頂級{{symbol}}存款者",
"total-borrows": "Total Borrows",
"total-deposits": "Total Deposits",
"total-supply": "總供應量", "total-supply": "總供應量",
"total-value": "全市值", "total-value": "全市值",
"volume": "24小時交易量" "volume": "24小時交易量"

117
utils/contentful.ts Normal file
View File

@ -0,0 +1,117 @@
import {
createClient,
Entry,
EntryFieldTypes,
EntrySkeletonType,
} from 'contentful'
import { Document as RichTextDocument } from '@contentful/rich-text-types'
interface TypeTokenFields {
tokenName: EntryFieldTypes.Symbol
slug: EntryFieldTypes.Symbol
seoTitle: EntryFieldTypes.Symbol
seoDescription: EntryFieldTypes.Text
description?: EntryFieldTypes.RichText
tags: EntryFieldTypes.Array<
EntryFieldTypes.Symbol<
| 'AI'
| 'Bridged (Portal)'
| 'DeFi'
| 'DePIN'
| 'Derivatives'
| 'Domains'
| 'Exchange'
| 'Gaming'
| 'Governance'
| 'Infrastructure'
| 'Layer 1'
| 'Liquid Staking'
| 'Meme'
| 'Payments'
| 'Social'
| 'Stablecoin'
>
>
websiteUrl?: EntryFieldTypes.Symbol
twitterUrl?: EntryFieldTypes.Symbol
whitepaper?: EntryFieldTypes.Symbol
mint: EntryFieldTypes.Symbol
coingeckoId: EntryFieldTypes.Symbol
symbol: EntryFieldTypes.Symbol
spotSymbol: EntryFieldTypes.Symbol
perpSymbol?: EntryFieldTypes.Symbol
ethMint?: EntryFieldTypes.Symbol
erc20TokenDecimals?: EntryFieldTypes.Integer
}
type TypeTokenSkeleton = EntrySkeletonType<TypeTokenFields, 'token'>
type TokenPageEntry = Entry<TypeTokenSkeleton, undefined, string>
export interface TokenPage {
tokenName: string
symbol: string
slug: string
description: RichTextDocument | undefined
tags: string[]
websiteUrl?: string
twitterUrl?: string
mint: string
ethMint: string | undefined
coingeckoId: string
seoTitle: string
seoDescription: string
perpSymbol: string | undefined
spotSymbol: string
lastModified: string
erc20TokenDecimals: number | undefined
}
function parseContentfulTokenPage(
tokenPageEntry?: TokenPageEntry,
): TokenPage | null {
if (!tokenPageEntry) {
return null
}
return {
tokenName: tokenPageEntry.fields.tokenName,
symbol: tokenPageEntry.fields.symbol,
slug: tokenPageEntry.fields.slug,
description: tokenPageEntry.fields.description || undefined,
tags: tokenPageEntry.fields.tags || [],
websiteUrl: tokenPageEntry.fields.websiteUrl || undefined,
twitterUrl: tokenPageEntry.fields.twitterUrl || undefined,
mint: tokenPageEntry.fields.mint,
ethMint: tokenPageEntry.fields.ethMint || undefined,
coingeckoId: tokenPageEntry.fields.coingeckoId,
seoTitle: tokenPageEntry.fields.seoTitle,
seoDescription: tokenPageEntry.fields.seoDescription,
perpSymbol: tokenPageEntry.fields.perpSymbol || undefined,
spotSymbol: tokenPageEntry.fields.spotSymbol,
lastModified: tokenPageEntry.sys.updatedAt,
erc20TokenDecimals: tokenPageEntry.fields.erc20TokenDecimals || undefined,
}
}
export async function fetchCMSTokenPage(
symbol: string | undefined,
): Promise<TokenPage[]> {
const client = createClient({
space: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID!,
accessToken: process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN!,
})
const tokenPagesResult = await client.getEntries({
content_type: 'token',
'fields.symbol[in]': symbol,
include: 2,
order: ['fields.tokenName'],
})
const parsedTokenPages = tokenPagesResult.items.map(
(tokenPageEntry) =>
parseContentfulTokenPage(tokenPageEntry as TokenPageEntry) as TokenPage,
)
return parsedTokenPages
}

View File

@ -408,6 +408,11 @@
near-api-js "^0.44.2" near-api-js "^0.44.2"
near-seed-phrase "^0.2.0" near-seed-phrase "^0.2.0"
"@contentful/rich-text-types@^16.0.2":
version "16.3.4"
resolved "https://registry.yarnpkg.com/@contentful/rich-text-types/-/rich-text-types-16.3.4.tgz#c5fc9c834dde03d4c4ee189900d304ce1888a74b"
integrity sha512-PyVSrQa5j1hO4grgA0Ivo/taiOvW0uFN79JB5JkTG8U7DnWGI7Ap2As6zN6/E6YvDqb7w2cYRMSGSQ3qfxu8HQ==
"@coral-xyz/anchor@0.28.1-beta.2", "@coral-xyz/anchor@^0.26.0", "@coral-xyz/anchor@^0.27.0", "@coral-xyz/anchor@^0.28.0", "@coral-xyz/anchor@^0.28.1-beta.2", "@coral-xyz/anchor@~0.27.0": "@coral-xyz/anchor@0.28.1-beta.2", "@coral-xyz/anchor@^0.26.0", "@coral-xyz/anchor@^0.27.0", "@coral-xyz/anchor@^0.28.0", "@coral-xyz/anchor@^0.28.1-beta.2", "@coral-xyz/anchor@~0.27.0":
version "0.27.0" version "0.27.0"
resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.27.0.tgz#621e5ef123d05811b97e49973b4ed7ede27c705c" resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.27.0.tgz#621e5ef123d05811b97e49973b4ed7ede27c705c"
@ -2572,7 +2577,7 @@
dependencies: dependencies:
"@solana/wallet-adapter-base" "^0.9.23" "@solana/wallet-adapter-base" "^0.9.23"
"@solana/wallet-adapter-solflare@0.6.27": "@solana/wallet-adapter-solflare@0.6.27", "@solana/wallet-adapter-solflare@^0.6.28":
version "0.6.27" version "0.6.27"
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.27.tgz#49ba2dfecca4bee048e65d302216d1b732d7e39e" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.27.tgz#49ba2dfecca4bee048e65d302216d1b732d7e39e"
integrity sha512-MBBx9B1pI8ChCT70sgxrmeib1S7G9tRQzfMHqJPdGQ2jGtukY0Puzma2OBsIAsH5Aw9rUUUFZUK+8pzaE+mgAg== integrity sha512-MBBx9B1pI8ChCT70sgxrmeib1S7G9tRQzfMHqJPdGQ2jGtukY0Puzma2OBsIAsH5Aw9rUUUFZUK+8pzaE+mgAg==
@ -2583,7 +2588,7 @@
"@solflare-wallet/sdk" "^1.3.0" "@solflare-wallet/sdk" "^1.3.0"
"@wallet-standard/wallet" "^1.0.1" "@wallet-standard/wallet" "^1.0.1"
"@solana/wallet-adapter-solflare@0.6.28", "@solana/wallet-adapter-solflare@^0.6.28": "@solana/wallet-adapter-solflare@0.6.28":
version "0.6.28" version "0.6.28"
resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.28.tgz#3de42a43220cca361050ebd1755078012a5b0fe2" resolved "https://registry.yarnpkg.com/@solana/wallet-adapter-solflare/-/wallet-adapter-solflare-0.6.28.tgz#3de42a43220cca361050ebd1755078012a5b0fe2"
integrity sha512-iiUQtuXp8p4OdruDawsm1dRRnzUCcsu+lKo8OezESskHtbmZw2Ifej0P99AbJbBAcBw7q4GPI6987Vh05Si5rw== integrity sha512-iiUQtuXp8p4OdruDawsm1dRRnzUCcsu+lKo8OezESskHtbmZw2Ifej0P99AbJbBAcBw7q4GPI6987Vh05Si5rw==
@ -4608,6 +4613,15 @@ axios@^1.1.3, axios@^1.4.0, axios@^1.6.2:
form-data "^4.0.0" form-data "^4.0.0"
proxy-from-env "^1.1.0" proxy-from-env "^1.1.0"
axios@^1.6.0:
version "1.6.7"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7"
integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==
dependencies:
follow-redirects "^1.15.4"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axobject-query@^3.1.1: axobject-query@^3.1.1:
version "3.2.1" version "3.2.1"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
@ -5420,6 +5434,36 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
contentful-resolve-response@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/contentful-resolve-response/-/contentful-resolve-response-1.8.1.tgz#b44ff13e12fab7deb00ef6216d8a7171bdda0395"
integrity sha512-VXGK2c8dBIGcRCknqudKmkDr2PzsUYfjLN6hhx71T09UzoXOdA/c0kfDhsf/BBCBWPWcLaUgaJEFU0lCo45TSg==
dependencies:
fast-copy "^2.1.7"
contentful-sdk-core@^8.1.0:
version "8.1.2"
resolved "https://registry.yarnpkg.com/contentful-sdk-core/-/contentful-sdk-core-8.1.2.tgz#a27ea57cfd631b4c6d58e5ca04fcde6d231e2c1b"
integrity sha512-XZvX2JMJF4YiICXLrHFv59KBHaQJ6ElqAP8gSNgnCu4x+pPG7Y1bC2JMNOiyAgJuGQGVUOcNZ5PmK+tsNEayYw==
dependencies:
fast-copy "^2.1.7"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
p-throttle "^4.1.1"
qs "^6.11.2"
contentful@10.6.21:
version "10.6.21"
resolved "https://registry.yarnpkg.com/contentful/-/contentful-10.6.21.tgz#7e2a8cae91f5f06297df27053538e937490035a7"
integrity sha512-ez3zNJ1A2dJTuoNxSkFhwjkhrQ/jYYTvc8jFeFIMwZuYMHjYwL+mFo1372pSASeJ6QAIf2srKOmukzCGiHtcSg==
dependencies:
"@contentful/rich-text-types" "^16.0.2"
axios "^1.6.0"
contentful-resolve-response "^1.8.1"
contentful-sdk-core "^8.1.0"
json-stringify-safe "^5.0.1"
type-fest "^4.0.0"
convert-source-map@^2.0.0: convert-source-map@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
@ -6775,6 +6819,11 @@ eyes@^0.1.8:
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==
fast-copy@^2.1.7:
version "2.1.7"
resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-2.1.7.tgz#affc9475cb4b555fb488572b2a44231d0c9fa39e"
integrity sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==
fast-copy@^3.0.1: fast-copy@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.1.tgz#9e89ef498b8c04c1cd76b33b8e14271658a732aa" resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.1.tgz#9e89ef498b8c04c1cd76b33b8e14271658a732aa"
@ -6917,6 +6966,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.7, fol
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
follow-redirects@^1.15.4:
version "1.15.5"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020"
integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==
for-each@^0.3.3: for-each@^0.3.3:
version "0.3.3" version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@ -8696,6 +8750,16 @@ lodash.isequal@4.5.0, lodash.isequal@^4.0.0, lodash.isequal@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
lodash.memoize@4.x: lodash.memoize@4.x:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@ -9886,6 +9950,11 @@ p-locate@^4.1.0:
dependencies: dependencies:
p-limit "^2.2.0" p-limit "^2.2.0"
p-throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/p-throttle/-/p-throttle-4.1.1.tgz#80b1fbd358af40a8bfa1667f9dc8b72b714ad692"
integrity sha512-TuU8Ato+pRTPJoDzYD4s7ocJYcNSEZRvlxoq3hcPI2kZDZ49IQ1Wkj7/gDJc3X7XiEAAvRGtDzdXJI0tC3IL1g==
p-try@^2.0.0: p-try@^2.0.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
@ -10297,6 +10366,13 @@ qrcode@1.4.4:
pngjs "^3.3.0" pngjs "^3.3.0"
yargs "^13.2.4" yargs "^13.2.4"
qs@^6.11.2:
version "6.11.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
dependencies:
side-channel "^1.0.4"
qs@~6.5.2: qs@~6.5.2:
version "6.5.3" version "6.5.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
@ -12255,6 +12331,11 @@ type-fest@^0.7.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
type-fest@^4.0.0:
version "4.10.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.10.2.tgz#3abdb144d93c5750432aac0d73d3e85fcab45738"
integrity sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==
typed-array-buffer@^1.0.0: typed-array-buffer@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60"