Merge pull request #274 from blockworks-foundation/mobile-tweaks
mobile market card (markets page)
This commit is contained in:
commit
11e6911671
|
@ -12,17 +12,23 @@ import { LinkButton } from './Button'
|
|||
import { ArrowSmDownIcon } from '@heroicons/react/solid'
|
||||
import { useRouter } from 'next/router'
|
||||
import { AreaChart, Area, XAxis, YAxis } from 'recharts'
|
||||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
import Tooltip from './Tooltip'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
const MarketsTable = ({ isPerpMarket }) => {
|
||||
const MarketsTable = ({
|
||||
isPerpMarket,
|
||||
markets,
|
||||
}: {
|
||||
isPerpMarket?: boolean
|
||||
markets: any[]
|
||||
}) => {
|
||||
const { t } = useTranslation('common')
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.md : false
|
||||
const marketsInfo = useMangoStore((s) => s.marketsInfo)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const coingeckoPrices = useMangoStore((s) => s.coingeckoPrices)
|
||||
const coingeckoPrices = useMangoStore((s) => s.coingeckoPrices.data)
|
||||
const loadingCoingeckoPrices = useMangoStore((s) => s.coingeckoPrices.loading)
|
||||
const router = useRouter()
|
||||
const { theme } = useTheme()
|
||||
|
||||
useEffect(() => {
|
||||
if (coingeckoPrices.length === 0) {
|
||||
|
@ -30,13 +36,383 @@ const MarketsTable = ({ isPerpMarket }) => {
|
|||
}
|
||||
}, [coingeckoPrices])
|
||||
|
||||
const perpMarketsInfo = useMemo(
|
||||
() =>
|
||||
marketsInfo
|
||||
.filter((mkt) => mkt?.name.includes('PERP'))
|
||||
.sort((a, b) => b.volumeUsd24h - a.volumeUsd24h),
|
||||
[marketsInfo]
|
||||
const { items, requestSort, sortConfig } = useSortableData(markets)
|
||||
|
||||
return !isMobile ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('name')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('market')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'name'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('last')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('price')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'last'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('change24h')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('rolling-change')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'change24h'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('volumeUsd24h')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('daily-volume')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'volumeUsd24h'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
{isPerpMarket ? (
|
||||
<>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('funding1h')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('average-funding')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'funding1h'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('openInterestUsd')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('open-interest')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'openInterestUsd'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
</>
|
||||
) : null}
|
||||
<Th>
|
||||
<span className="flex justify-end">{t('favorite')}</span>
|
||||
</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((market) => {
|
||||
const {
|
||||
baseSymbol,
|
||||
change24h,
|
||||
funding1h,
|
||||
last,
|
||||
name,
|
||||
openInterest,
|
||||
openInterestUsd,
|
||||
volumeUsd24h,
|
||||
} = market
|
||||
const fundingApr = funding1h ? (funding1h * 24 * 365).toFixed(2) : '-'
|
||||
const coingeckoData = coingeckoPrices.find(
|
||||
(asset) => asset.symbol === baseSymbol
|
||||
)
|
||||
const chartData = coingeckoData ? coingeckoData.prices : undefined
|
||||
const chartColor =
|
||||
change24h >= 0
|
||||
? theme === 'Mango'
|
||||
? '#AFD803'
|
||||
: '#5EBF4D'
|
||||
: theme === 'Mango'
|
||||
? '#F84638'
|
||||
: '#CC2929'
|
||||
return (
|
||||
<TrBody key={name} className="hover:bg-th-bkg-3">
|
||||
<Td>
|
||||
<Link href={`/?name=${name}`} shallow={true}>
|
||||
<a className="hover:cursor-pointer">
|
||||
<div className="flex h-full items-center text-th-fgd-2 hover:text-th-primary">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<span className="default-transition">{name}</span>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</Td>
|
||||
<Td className="flex items-center">
|
||||
<div className="w-20">
|
||||
{last ? (
|
||||
formatUsdValue(last)
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="pl-6">
|
||||
{!loadingCoingeckoPrices ? (
|
||||
chartData !== undefined ? (
|
||||
<PriceChart
|
||||
color={chartColor}
|
||||
data={chartData}
|
||||
height={40}
|
||||
width={104}
|
||||
/>
|
||||
) : (
|
||||
t('unavailable')
|
||||
)
|
||||
) : (
|
||||
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<span
|
||||
className={change24h >= 0 ? 'text-th-green' : 'text-th-red'}
|
||||
>
|
||||
{change24h || change24h === 0 ? (
|
||||
`${(change24h * 100).toFixed(2)}%`
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</span>
|
||||
</Td>
|
||||
<Td>
|
||||
{volumeUsd24h ? (
|
||||
usdFormatter(volumeUsd24h, 0)
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</Td>
|
||||
{isPerpMarket ? (
|
||||
<>
|
||||
<Td>
|
||||
{funding1h ? (
|
||||
<>
|
||||
<span>{`${funding1h.toFixed(4)}%`}</span>{' '}
|
||||
<span className="text-xs text-th-fgd-3">{`(${fundingApr}% APR)`}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{openInterestUsd ? (
|
||||
<>
|
||||
<span>{usdFormatter(openInterestUsd, 0)}</span>{' '}
|
||||
{openInterest ? (
|
||||
<div className="text-xs text-th-fgd-4">
|
||||
{openInterest.toLocaleString(undefined, {
|
||||
maximumFractionDigits:
|
||||
perpContractPrecision[baseSymbol],
|
||||
})}{' '}
|
||||
{baseSymbol}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</Td>
|
||||
</>
|
||||
) : null}
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
<FavoriteMarketButton market={market} />
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<>
|
||||
{items.map((market) => {
|
||||
const { baseSymbol, change24h, funding1h, last, name } = market
|
||||
const fundingApr = funding1h ? (funding1h * 24 * 365).toFixed(2) : '-'
|
||||
const coingeckoData = coingeckoPrices.find(
|
||||
(asset) => asset.symbol === baseSymbol
|
||||
)
|
||||
const chartData = coingeckoData ? coingeckoData.prices : undefined
|
||||
const chartColor =
|
||||
change24h >= 0
|
||||
? theme === 'Mango'
|
||||
? '#AFD803'
|
||||
: '#5EBF4D'
|
||||
: theme === 'Mango'
|
||||
? '#F84638'
|
||||
: '#CC2929'
|
||||
return (
|
||||
<Link href={`/?name=${name}`} shallow={true} key={name}>
|
||||
<a
|
||||
className="mb-2 block w-full rounded-lg bg-th-bkg-3 p-4 pb-2.5"
|
||||
onClick={() =>
|
||||
router.push(`/?name=${name}`, undefined, {
|
||||
shallow: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="mb-1 flex justify-between">
|
||||
<div>
|
||||
<div className="mb-2 flex items-center font-bold text-th-fgd-2">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
|
||||
className="mr-2"
|
||||
/>
|
||||
|
||||
{name}
|
||||
<div
|
||||
className={`ml-3 ${
|
||||
change24h >= 0 ? 'text-th-green' : 'text-th-red'
|
||||
}`}
|
||||
>
|
||||
{change24h || change24h === 0 ? (
|
||||
`${(change24h * 100).toFixed(2)}%`
|
||||
) : (
|
||||
<span className="text-th-fgd-4">
|
||||
{t('unavailable')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!loadingCoingeckoPrices ? (
|
||||
chartData !== undefined ? (
|
||||
<PriceChart
|
||||
color={chartColor}
|
||||
data={chartData}
|
||||
height={48}
|
||||
width={128}
|
||||
/>
|
||||
) : (
|
||||
t('unavailable')
|
||||
)
|
||||
) : (
|
||||
<div className="h-12 w-[128px] animate-pulse rounded bg-th-bkg-4" />
|
||||
)}
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="mb-0 mb-3 text-xl font-bold leading-none text-th-fgd-2">
|
||||
{last ? (
|
||||
formatUsdValue(last)
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</p>
|
||||
{isPerpMarket ? (
|
||||
funding1h ? (
|
||||
<div className="mt-1 justify-end text-th-fgd-3">
|
||||
<div className="text-[10px] leading-tight text-th-fgd-4">
|
||||
{t('average-funding')}
|
||||
</div>
|
||||
<span className="text-xs">{`${fundingApr}% APR`}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const PriceChart = ({ data, width, height, color }) => (
|
||||
<AreaChart width={width} height={height} data={data}>
|
||||
<defs>
|
||||
<linearGradient id="gradientArea" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor={color} stopOpacity={0.6} />
|
||||
<stop offset="100%" stopColor={color} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
isAnimationActive={false}
|
||||
type="monotone"
|
||||
dataKey="1"
|
||||
stroke={color}
|
||||
fill="url(#gradientArea)"
|
||||
/>
|
||||
<XAxis dataKey="0" hide />
|
||||
<YAxis domain={['dataMin', 'dataMax']} dataKey="1" hide />
|
||||
</AreaChart>
|
||||
)
|
||||
|
||||
export const SpotMarketsTable = () => {
|
||||
const marketsInfo = useMangoStore((s) => s.marketsInfo)
|
||||
|
||||
const spotMarketsInfo = useMemo(
|
||||
() =>
|
||||
|
@ -45,355 +421,18 @@ const MarketsTable = ({ isPerpMarket }) => {
|
|||
.sort((a, b) => b.volumeUsd24h - a.volumeUsd24h),
|
||||
[marketsInfo]
|
||||
)
|
||||
|
||||
const { items, requestSort, sortConfig } = useSortableData(
|
||||
isPerpMarket ? perpMarketsInfo : spotMarketsInfo
|
||||
)
|
||||
|
||||
return items.length > 0 ? (
|
||||
!isMobile ? (
|
||||
<Table>
|
||||
<thead>
|
||||
<TrHead>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('name')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('market')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'name'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('last')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('price')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'last'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('change24h')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('rolling-change')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'change24h'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('volumeUsd24h')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('daily-volume')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'volumeUsd24h'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
{isPerpMarket ? (
|
||||
<>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center font-normal no-underline"
|
||||
onClick={() => requestSort('funding1h')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('average-funding')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'funding1h'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
<Th>
|
||||
<LinkButton
|
||||
className="flex items-center no-underline"
|
||||
onClick={() => requestSort('openInterestUsd')}
|
||||
>
|
||||
<span className="text-left font-normal text-th-fgd-3">
|
||||
{t('open-interest')}
|
||||
</span>
|
||||
<ArrowSmDownIcon
|
||||
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
|
||||
sortConfig?.key === 'openInterestUsd'
|
||||
? sortConfig.direction === 'ascending'
|
||||
? 'rotate-180 transform'
|
||||
: 'rotate-360 transform'
|
||||
: null
|
||||
}`}
|
||||
/>
|
||||
</LinkButton>
|
||||
</Th>
|
||||
</>
|
||||
) : null}
|
||||
<Th>
|
||||
<span className="flex justify-end">{t('favorite')}</span>
|
||||
</Th>
|
||||
</TrHead>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((market) => {
|
||||
const {
|
||||
baseSymbol,
|
||||
change24h,
|
||||
funding1h,
|
||||
last,
|
||||
name,
|
||||
openInterest,
|
||||
openInterestUsd,
|
||||
volumeUsd24h,
|
||||
} = market
|
||||
const fundingApr = funding1h
|
||||
? (funding1h * 24 * 365).toFixed(2)
|
||||
: '-'
|
||||
const coingeckoData = coingeckoPrices.find(
|
||||
(asset) => asset.symbol === baseSymbol
|
||||
)
|
||||
const chartData = coingeckoData ? coingeckoData.prices : undefined
|
||||
return (
|
||||
<TrBody key={name} className="hover:bg-th-bkg-3">
|
||||
<Td>
|
||||
<Link href={`/?name=${name}`} shallow={true}>
|
||||
<a className="hover:cursor-pointer">
|
||||
<div className="flex h-full items-center text-th-fgd-2 hover:text-th-primary">
|
||||
<img
|
||||
alt=""
|
||||
width="20"
|
||||
height="20"
|
||||
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
|
||||
className={`mr-2.5`}
|
||||
/>
|
||||
<span className="default-transition">{name}</span>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</Td>
|
||||
<Td className="flex items-center">
|
||||
<div className="w-20">
|
||||
{last ? (
|
||||
formatUsdValue(last)
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="pl-6">
|
||||
{chartData !== undefined ? (
|
||||
<AreaChart width={104} height={40} data={chartData}>
|
||||
<Area
|
||||
isAnimationActive={false}
|
||||
type="monotone"
|
||||
dataKey="1"
|
||||
stroke="#FF9C24"
|
||||
fill="#FF9C24"
|
||||
fillOpacity={0.1}
|
||||
/>
|
||||
<XAxis dataKey="0" hide />
|
||||
<YAxis
|
||||
domain={['dataMin', 'dataMax']}
|
||||
dataKey="1"
|
||||
hide
|
||||
/>
|
||||
</AreaChart>
|
||||
) : (
|
||||
t('unavailable')
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<span
|
||||
className={change24h >= 0 ? 'text-th-green' : 'text-th-red'}
|
||||
>
|
||||
{change24h || change24h === 0 ? (
|
||||
`${(change24h * 100).toFixed(2)}%`
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</span>
|
||||
</Td>
|
||||
<Td>
|
||||
{volumeUsd24h ? (
|
||||
usdFormatter(volumeUsd24h, 0)
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</Td>
|
||||
{isPerpMarket ? (
|
||||
<>
|
||||
<Td>
|
||||
{funding1h ? (
|
||||
<>
|
||||
<span>{`${funding1h.toFixed(4)}%`}</span>{' '}
|
||||
<span className="text-xs text-th-fgd-3">{`(${fundingApr}% APR)`}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-th-fgd-4">
|
||||
{t('unavailable')}
|
||||
</span>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{openInterestUsd ? (
|
||||
<>
|
||||
<span>{usdFormatter(openInterestUsd, 0)}</span>{' '}
|
||||
{openInterest ? (
|
||||
<div className="text-xs text-th-fgd-4">
|
||||
{openInterest.toLocaleString(undefined, {
|
||||
maximumFractionDigits:
|
||||
perpContractPrecision[baseSymbol],
|
||||
})}{' '}
|
||||
{baseSymbol}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-th-fgd-4">
|
||||
{t('unavailable')}
|
||||
</span>
|
||||
)}
|
||||
</Td>
|
||||
</>
|
||||
) : null}
|
||||
<Td>
|
||||
<div className="flex justify-end">
|
||||
<FavoriteMarketButton market={market} />
|
||||
</div>
|
||||
</Td>
|
||||
</TrBody>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Table>
|
||||
) : (
|
||||
items.map((market) => {
|
||||
const { baseSymbol, change24h, funding1h, last, name } = market
|
||||
const fundingApr = funding1h ? (funding1h * 24 * 365).toFixed(2) : '-'
|
||||
const coingeckoData = coingeckoPrices.find(
|
||||
(asset) => asset.symbol === baseSymbol
|
||||
)
|
||||
const chartData = coingeckoData ? coingeckoData.prices : undefined
|
||||
return (
|
||||
<button
|
||||
className="mb-2.5 w-full rounded-lg bg-th-bkg-2 p-4 pb-2.5 md:bg-th-bkg-3"
|
||||
onClick={() =>
|
||||
router.push(`/?name=${name}`, undefined, {
|
||||
shallow: true,
|
||||
})
|
||||
}
|
||||
key={name}
|
||||
>
|
||||
<div className="mb-1 flex justify-between">
|
||||
<div>
|
||||
<div className="mb-2 flex items-center font-bold text-th-fgd-3">
|
||||
<img
|
||||
alt=""
|
||||
width="24"
|
||||
height="24"
|
||||
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
|
||||
className="mr-2"
|
||||
/>
|
||||
|
||||
{name}
|
||||
</div>
|
||||
{chartData !== undefined ? (
|
||||
<AreaChart width={144} height={40} data={chartData}>
|
||||
<Area
|
||||
isAnimationActive={false}
|
||||
type="monotone"
|
||||
dataKey="1"
|
||||
stroke="#FF9C24"
|
||||
fill="#FF9C24"
|
||||
fillOpacity={0.1}
|
||||
/>
|
||||
<XAxis dataKey="0" hide />
|
||||
<YAxis domain={['dataMin', 'dataMax']} dataKey="1" hide />
|
||||
</AreaChart>
|
||||
) : (
|
||||
t('unavailable')
|
||||
)}
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="mb-0 text-xl font-bold">
|
||||
{last ? (
|
||||
formatUsdValue(last)
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</p>
|
||||
<div
|
||||
className={change24h >= 0 ? 'text-th-green' : 'text-th-red'}
|
||||
>
|
||||
{change24h || change24h === 0 ? (
|
||||
`${(change24h * 100).toFixed(2)}%`
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)}
|
||||
</div>
|
||||
{isPerpMarket ? (
|
||||
funding1h ? (
|
||||
<Tooltip content={t('average-funding')}>
|
||||
<div className="mt-1 flex items-center justify-end text-th-fgd-3">
|
||||
<span className="text-xs">{`${fundingApr}% APR`}</span>
|
||||
<InformationCircleIcon className="ml-1 h-4 w-4" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span className="text-th-fgd-4">{t('unavailable')}</span>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
)
|
||||
) : null
|
||||
return <MarketsTable markets={spotMarketsInfo} />
|
||||
}
|
||||
|
||||
export default MarketsTable as any
|
||||
export const PerpMarketsTable = () => {
|
||||
const marketsInfo = useMangoStore((s) => s.marketsInfo)
|
||||
const perpMarketsInfo = useMemo(
|
||||
() =>
|
||||
marketsInfo
|
||||
.filter((mkt) => mkt?.name.includes('PERP'))
|
||||
.sort((a, b) => b.volumeUsd24h - a.volumeUsd24h),
|
||||
[marketsInfo]
|
||||
)
|
||||
|
||||
return <MarketsTable isPerpMarket markets={perpMarketsInfo} />
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ const BottomBar = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="default-transition grid grid-cols-5 grid-rows-1 bg-th-bkg-3 py-2.5">
|
||||
<div className="default-transition grid grid-cols-5 grid-rows-1 bg-th-bkg-4 py-2.5">
|
||||
<Link
|
||||
href={{
|
||||
pathname: '/markets',
|
||||
|
|
|
@ -6,6 +6,7 @@ import useMangoStore from '../../stores/useMangoStore'
|
|||
import { getWeights, PerpMarket } from '@blockworks-foundation/mango-client'
|
||||
import { CandlesIcon } from '../icons'
|
||||
import SwipeableTabs from './SwipeableTabs'
|
||||
import Swipeable from './Swipeable'
|
||||
import AdvancedTradeForm from '../trade_form/AdvancedTradeForm'
|
||||
import Orderbook from '../Orderbook'
|
||||
import MarketBalances from '../MarketBalances'
|
||||
|
@ -14,7 +15,6 @@ import MarketPosition from '../MarketPosition'
|
|||
import OpenOrdersTable from '../OpenOrdersTable'
|
||||
import RecentMarketTrades from '../RecentMarketTrades'
|
||||
import FloatingElement from '../FloatingElement'
|
||||
import Swipeable from './Swipeable'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useWallet } from '@solana/wallet-adapter-react'
|
||||
import SwitchMarketDropdown from 'components/SwitchMarketDropdown'
|
||||
|
|
|
@ -3,8 +3,12 @@ import PageBodyContainer from '../components/PageBodyContainer'
|
|||
import TopBar from '../components/TopBar'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import MarketsTable from '../components/MarketsTable'
|
||||
import { PerpMarketsTable, SpotMarketsTable } from '../components/MarketsTable'
|
||||
import Tabs from '../components/Tabs'
|
||||
import SwipeableTabs from '../components/mobile/SwipeableTabs'
|
||||
import Swipeable from '../components/mobile/Swipeable'
|
||||
import { useViewport } from '../hooks/useViewport'
|
||||
import { breakpoints } from '../components/TradePageGrid'
|
||||
|
||||
const TABS = ['futures', 'spot']
|
||||
|
||||
|
@ -19,12 +23,19 @@ export async function getStaticProps({ locale }) {
|
|||
|
||||
export default function Markets() {
|
||||
const [activeTab, setActiveTab] = useState('futures')
|
||||
const [viewIndex, setViewIndex] = useState(0)
|
||||
const { t } = useTranslation(['common'])
|
||||
const { width } = useViewport()
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
||||
const handleTabChange = (tabName) => {
|
||||
setActiveTab(tabName)
|
||||
}
|
||||
|
||||
const handleChangeViewIndex = (index) => {
|
||||
setViewIndex(index)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
|
||||
<TopBar />
|
||||
|
@ -34,13 +45,36 @@ export default function Markets() {
|
|||
</div>
|
||||
<div className="md:rounded-lg md:bg-th-bkg-2 md:p-6">
|
||||
<div className="mb-0 sm:mb-6">
|
||||
<Tabs
|
||||
activeTab={activeTab}
|
||||
onChange={handleTabChange}
|
||||
tabs={TABS}
|
||||
/>
|
||||
{!isMobile ? (
|
||||
<Tabs
|
||||
activeTab={activeTab}
|
||||
onChange={handleTabChange}
|
||||
tabs={TABS}
|
||||
/>
|
||||
) : (
|
||||
<SwipeableTabs
|
||||
onChange={handleChangeViewIndex}
|
||||
tabs={TABS}
|
||||
tabIndex={viewIndex}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<MarketsTable isPerpMarket={activeTab === 'futures'} />
|
||||
{!isMobile ? (
|
||||
activeTab === 'futures' ? (
|
||||
<PerpMarketsTable />
|
||||
) : (
|
||||
<SpotMarketsTable />
|
||||
)
|
||||
) : (
|
||||
<Swipeable index={viewIndex} onChangeIndex={handleChangeViewIndex}>
|
||||
<div className="px-1">
|
||||
<PerpMarketsTable />
|
||||
</div>
|
||||
<div className="px-1">
|
||||
<SpotMarketsTable />
|
||||
</div>
|
||||
</Swipeable>
|
||||
)}
|
||||
</div>
|
||||
</PageBodyContainer>
|
||||
</div>
|
||||
|
|
|
@ -279,7 +279,7 @@ export type MangoStore = {
|
|||
tradingView: {
|
||||
orderLines: Map<string, IOrderLineAdapter>
|
||||
}
|
||||
coingeckoPrices: any[]
|
||||
coingeckoPrices: { data: any[]; loading: boolean }
|
||||
}
|
||||
|
||||
const useMangoStore = create<
|
||||
|
@ -411,7 +411,7 @@ const useMangoStore = create<
|
|||
tradingView: {
|
||||
orderLines: new Map(),
|
||||
},
|
||||
coingeckoPrices: [],
|
||||
coingeckoPrices: { data: [], loading: false },
|
||||
set: (fn) => set(produce(fn)),
|
||||
actions: {
|
||||
async fetchWalletTokens(wallet: Wallet) {
|
||||
|
@ -1131,12 +1131,15 @@ const useMangoStore = create<
|
|||
},
|
||||
async fetchCoingeckoPrices() {
|
||||
const set = get().set
|
||||
set((state) => {
|
||||
state.coingeckoPrices.loading = true
|
||||
})
|
||||
try {
|
||||
const promises: any = []
|
||||
for (const asset of coingeckoIds) {
|
||||
promises.push(
|
||||
fetch(
|
||||
`https://api.coingecko.com/api/v3/coins/${asset.id}/market_chart?vs_currency=usd&days=2`
|
||||
`https://api.coingecko.com/api/v3/coins/${asset.id}/market_chart?vs_currency=usd&days=1`
|
||||
).then((res) => res.json())
|
||||
)
|
||||
}
|
||||
|
@ -1146,10 +1149,14 @@ const useMangoStore = create<
|
|||
data[i].symbol = coingeckoIds[i].symbol
|
||||
}
|
||||
set((state) => {
|
||||
state.coingeckoPrices = data
|
||||
state.coingeckoPrices.data = data
|
||||
state.coingeckoPrices.loading = false
|
||||
})
|
||||
} catch (e) {
|
||||
console.log('ERORR: Unable to load Coingecko prices')
|
||||
set((state) => {
|
||||
state.coingeckoPrices.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
// const colors = require('tailwindcss/colors')
|
||||
// const defaultTheme = require('tailwindcss/defaultTheme')
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./pages/**/*.{js,ts,jsx,tsx}',
|
||||
|
|
Loading…
Reference in New Issue