Merge pull request #270 from blockworks-foundation/mobile-tweaks

market selection on mobile
This commit is contained in:
tjshipe 2022-05-17 13:37:18 -04:00 committed by GitHub
commit 57732372d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 265 additions and 365 deletions

View File

@ -91,7 +91,7 @@ const MarketNavItem: FunctionComponent<MarketNavItemProps> = ({
</div> </div>
) : null} ) : null}
</button> </button>
<div className="ml-1"> <div className="ml-1 hidden sm:block">
<FavoriteMarketButton market={market} /> <FavoriteMarketButton market={market} />
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import { useMemo } from 'react' import { useEffect, useMemo } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { formatUsdValue, perpContractPrecision, usdFormatter } from '../utils' import { formatUsdValue, perpContractPrecision, usdFormatter } from '../utils'
import { Table, Td, Th, TrBody, TrHead } from './TableElements' import { Table, Td, Th, TrBody, TrHead } from './TableElements'
@ -6,18 +6,29 @@ import { useViewport } from '../hooks/useViewport'
import { breakpoints } from './TradePageGrid' import { breakpoints } from './TradePageGrid'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import useMangoStore from '../stores/useMangoStore' import useMangoStore from '../stores/useMangoStore'
import MobileTableHeader from './mobile/MobileTableHeader'
import { ExpandableRow } from './TableElements'
import { FavoriteMarketButton } from './TradeNavMenu' import { FavoriteMarketButton } from './TradeNavMenu'
import { useSortableData } from '../hooks/useSortableData' import { useSortableData } from '../hooks/useSortableData'
import { LinkButton } from './Button' import { LinkButton } from './Button'
import { ArrowSmDownIcon } from '@heroicons/react/solid' 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'
const MarketsTable = ({ isPerpMarket }) => { const MarketsTable = ({ isPerpMarket }) => {
const { t } = useTranslation('common') const { t } = useTranslation('common')
const { width } = useViewport() const { width } = useViewport()
const isMobile = width ? width < breakpoints.md : false const isMobile = width ? width < breakpoints.md : false
const marketsInfo = useMangoStore((s) => s.marketsInfo) const marketsInfo = useMangoStore((s) => s.marketsInfo)
const actions = useMangoStore((s) => s.actions)
const coingeckoPrices = useMangoStore((s) => s.coingeckoPrices)
const router = useRouter()
useEffect(() => {
if (coingeckoPrices.length === 0) {
actions.fetchCoingeckoPrices()
}
}, [coingeckoPrices])
const perpMarketsInfo = useMemo( const perpMarketsInfo = useMemo(
() => () =>
@ -49,7 +60,9 @@ const MarketsTable = ({ isPerpMarket }) => {
className="flex items-center font-normal no-underline" className="flex items-center font-normal no-underline"
onClick={() => requestSort('name')} onClick={() => requestSort('name')}
> >
<span className="font-normal text-th-fgd-3">{t('market')}</span> <span className="text-left font-normal text-th-fgd-3">
{t('market')}
</span>
<ArrowSmDownIcon <ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${ className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'name' sortConfig?.key === 'name'
@ -66,7 +79,9 @@ const MarketsTable = ({ isPerpMarket }) => {
className="flex items-center font-normal no-underline" className="flex items-center font-normal no-underline"
onClick={() => requestSort('last')} onClick={() => requestSort('last')}
> >
<span className="font-normal text-th-fgd-3">{t('price')}</span> <span className="text-left font-normal text-th-fgd-3">
{t('price')}
</span>
<ArrowSmDownIcon <ArrowSmDownIcon
className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${ className={`default-transition ml-1 h-4 w-4 flex-shrink-0 ${
sortConfig?.key === 'last' sortConfig?.key === 'last'
@ -83,7 +98,7 @@ const MarketsTable = ({ isPerpMarket }) => {
className="flex items-center font-normal no-underline" className="flex items-center font-normal no-underline"
onClick={() => requestSort('change24h')} onClick={() => requestSort('change24h')}
> >
<span className="font-normal text-th-fgd-3"> <span className="text-left font-normal text-th-fgd-3">
{t('rolling-change')} {t('rolling-change')}
</span> </span>
<ArrowSmDownIcon <ArrowSmDownIcon
@ -102,7 +117,7 @@ const MarketsTable = ({ isPerpMarket }) => {
className="flex items-center font-normal no-underline" className="flex items-center font-normal no-underline"
onClick={() => requestSort('volumeUsd24h')} onClick={() => requestSort('volumeUsd24h')}
> >
<span className="font-normal text-th-fgd-3"> <span className="text-left font-normal text-th-fgd-3">
{t('daily-volume')} {t('daily-volume')}
</span> </span>
<ArrowSmDownIcon <ArrowSmDownIcon
@ -123,7 +138,7 @@ const MarketsTable = ({ isPerpMarket }) => {
className="flex items-center font-normal no-underline" className="flex items-center font-normal no-underline"
onClick={() => requestSort('funding1h')} onClick={() => requestSort('funding1h')}
> >
<span className="font-normal text-th-fgd-3"> <span className="text-left font-normal text-th-fgd-3">
{t('average-funding')} {t('average-funding')}
</span> </span>
<ArrowSmDownIcon <ArrowSmDownIcon
@ -142,7 +157,7 @@ const MarketsTable = ({ isPerpMarket }) => {
className="flex items-center no-underline" className="flex items-center no-underline"
onClick={() => requestSort('openInterestUsd')} onClick={() => requestSort('openInterestUsd')}
> >
<span className="font-normal text-th-fgd-3"> <span className="text-left font-normal text-th-fgd-3">
{t('open-interest')} {t('open-interest')}
</span> </span>
<ArrowSmDownIcon <ArrowSmDownIcon
@ -178,7 +193,10 @@ const MarketsTable = ({ isPerpMarket }) => {
const fundingApr = funding1h const fundingApr = funding1h
? (funding1h * 24 * 365).toFixed(2) ? (funding1h * 24 * 365).toFixed(2)
: '-' : '-'
const coingeckoData = coingeckoPrices.find(
(asset) => asset.symbol === baseSymbol
)
const chartData = coingeckoData ? coingeckoData.prices : undefined
return ( return (
<TrBody key={name} className="hover:bg-th-bkg-3"> <TrBody key={name} className="hover:bg-th-bkg-3">
<Td> <Td>
@ -197,12 +215,36 @@ const MarketsTable = ({ isPerpMarket }) => {
</a> </a>
</Link> </Link>
</Td> </Td>
<Td> <Td className="flex items-center">
{last ? ( <div className="w-20">
formatUsdValue(last) {last ? (
) : ( formatUsdValue(last)
<span className="text-th-fgd-4">Unavailable</span> ) : (
)} <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>
<Td> <Td>
<span <span
@ -211,7 +253,7 @@ const MarketsTable = ({ isPerpMarket }) => {
{change24h || change24h === 0 ? ( {change24h || change24h === 0 ? (
`${(change24h * 100).toFixed(2)}%` `${(change24h * 100).toFixed(2)}%`
) : ( ) : (
<span className="text-th-fgd-4">Unavailable</span> <span className="text-th-fgd-4">{t('unavailable')}</span>
)} )}
</span> </span>
</Td> </Td>
@ -219,7 +261,7 @@ const MarketsTable = ({ isPerpMarket }) => {
{volumeUsd24h ? ( {volumeUsd24h ? (
usdFormatter(volumeUsd24h, 0) usdFormatter(volumeUsd24h, 0)
) : ( ) : (
<span className="text-th-fgd-4">Unavailable</span> <span className="text-th-fgd-4">{t('unavailable')}</span>
)} )}
</Td> </Td>
{isPerpMarket ? ( {isPerpMarket ? (
@ -231,7 +273,9 @@ const MarketsTable = ({ isPerpMarket }) => {
<span className="text-xs text-th-fgd-3">{`(${fundingApr}% APR)`}</span> <span className="text-xs text-th-fgd-3">{`(${fundingApr}% APR)`}</span>
</> </>
) : ( ) : (
<span className="text-th-fgd-4">Unavailable</span> <span className="text-th-fgd-4">
{t('unavailable')}
</span>
)} )}
</Td> </Td>
<Td> <Td>
@ -249,7 +293,9 @@ const MarketsTable = ({ isPerpMarket }) => {
) : null} ) : null}
</> </>
) : ( ) : (
<span className="text-th-fgd-4">Unavailable</span> <span className="text-th-fgd-4">
{t('unavailable')}
</span>
)} )}
</Td> </Td>
</> </>
@ -265,128 +311,89 @@ const MarketsTable = ({ isPerpMarket }) => {
</tbody> </tbody>
</Table> </Table>
) : ( ) : (
<div className="mb-4 border-b border-th-bkg-4"> items.map((market) => {
<MobileTableHeader const { baseSymbol, change24h, funding1h, last, name } = market
colOneHeader={t('asset')} const fundingApr = funding1h ? (funding1h * 24 * 365).toFixed(2) : '-'
colTwoHeader={`${t('price')}/${t('rolling-change')}`} const coingeckoData = coingeckoPrices.find(
/> (asset) => asset.symbol === baseSymbol
{items.map((market, index) => { )
const { const chartData = coingeckoData ? coingeckoData.prices : undefined
baseSymbol, return (
change24h, <button
funding1h, className="mb-2.5 w-full rounded-lg bg-th-bkg-2 p-4 pb-2.5 md:bg-th-bkg-3"
high24h, onClick={() =>
last, router.push(`/?name=${name}`, undefined, {
low24h, shallow: true,
name, })
openInterest, }
volumeUsd24h, key={name}
} = market >
const fundingApr = funding1h ? (funding1h * 24 * 365).toFixed(2) : '-' <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"
/>
return ( {name}
<ExpandableRow
buttonTemplate={
<div className="flex w-full items-center justify-between text-th-fgd-1">
<div className="flex items-center text-th-fgd-1">
<img
alt=""
width="20"
height="20"
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
className={`mr-2.5`}
/>
{market.baseSymbol}
</div>
<div className="flex space-x-2.5 text-right text-th-fgd-1">
<div>{formatUsdValue(last)}</div>
<div className="text-th-fgd-4">|</div>
<div
className={
change24h >= 0 ? 'text-th-green' : 'text-th-red'
}
>
{change24h || change24h === 0 ? (
`${(change24h * 100).toFixed(2)}%`
) : (
<span className="text-th-fgd-4">Unavailable</span>
)}
</div>
</div>
</div> </div>
} {chartData !== undefined ? (
key={`${name}${index}`} <AreaChart width={144} height={40} data={chartData}>
panelTemplate={ <Area
<> isAnimationActive={false}
<div className="grid grid-flow-row grid-cols-2 gap-4 pb-4"> type="monotone"
<div className="text-left"> dataKey="1"
<div className="pb-0.5 text-xs text-th-fgd-3"> stroke="#FF9C24"
{t('daily-low')} 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> </div>
{low24h ? ( </Tooltip>
formatUsdValue(low24h) ) : (
) : ( <span className="text-th-fgd-4">{t('unavailable')}</span>
<span className="text-th-fgd-4">Unavailable</span> )
)} ) : null}
</div> </div>
<div className="text-left"> </div>
<div className="pb-0.5 text-xs text-th-fgd-3"> </button>
{t('daily-high')} )
</div> })
{high24h ? (
formatUsdValue(high24h)
) : (
<span className="text-th-fgd-4">Unavailable</span>
)}
</div>
{isPerpMarket ? (
<>
<div className="text-left">
<div className="pb-0.5 text-xs text-th-fgd-3">
{t('daily-volume')}
</div>
{volumeUsd24h ? (
usdFormatter(volumeUsd24h, 0)
) : (
<span className="text-th-fgd-4">Unavailable</span>
)}
</div>
<div className="text-left">
<div className="pb-0.5 text-xs text-th-fgd-3">
{t('average-funding')}
</div>
{funding1h ? (
`${funding1h.toLocaleString(undefined, {
maximumSignificantDigits: 3,
})}% (${fundingApr}% APR)`
) : (
<span className="text-th-fgd-4">Unavailable</span>
)}
</div>
<div className="text-left">
<div className="pb-0.5 text-xs text-th-fgd-3">
{t('open-interest')}
</div>
{openInterest ? (
`${openInterest.toLocaleString()} ${
market.baseSymbol
}`
) : (
<span className="text-th-fgd-4">Unavailable</span>
)}
</div>
</>
) : null}
</div>
</>
}
/>
)
})}
</div>
) )
) : null ) : null
} }
export default MarketsTable export default MarketsTable as any

View File

@ -523,27 +523,18 @@ export default function Orderbook({ depth = 8 }) {
<div> <div>
<div className="flex items-center justify-between pb-2.5"> <div className="flex items-center justify-between pb-2.5">
<div className="relative flex"> <div className="relative flex">
<Tooltip <button
content={ onClick={() => {
displayCumulativeSize setDisplayCumulativeSize(!displayCumulativeSize)
? t('tooltip-display-step') }}
: t('tooltip-display-cumulative') className="flex h-8 w-8 items-center justify-center rounded-full bg-th-bkg-3 hover:text-th-primary focus:outline-none"
}
className="py-1 text-xs"
> >
<button {displayCumulativeSize ? (
onClick={() => { <StepSizeIcon className="h-5 w-5" />
setDisplayCumulativeSize(!displayCumulativeSize) ) : (
}} <CumulativeSizeIcon className="h-5 w-5" />
className="flex h-8 w-8 items-center justify-center rounded-full bg-th-bkg-3 hover:text-th-primary focus:outline-none" )}
> </button>
{displayCumulativeSize ? (
<StepSizeIcon className="h-5 w-5" />
) : (
<CumulativeSizeIcon className="h-5 w-5" />
)}
</button>
</Tooltip>
</div> </div>
<GroupSize <GroupSize

View File

@ -103,10 +103,10 @@ const SwitchMarketDropdown = () => {
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Popover.Panel <Popover.Panel
className="thin-scroll absolute left-0 top-14 z-10 max-h-[75vh] w-72 transform overflow-y-auto rounded-b-md rounded-tl-md bg-th-bkg-3 p-4" className="thin-scroll absolute left-0 top-14 z-10 max-h-[50vh] w-72 transform overflow-y-auto rounded-b-md rounded-tl-md bg-th-bkg-3 p-4 sm:max-h-[75vh]"
tabIndex={-1} tabIndex={-1}
> >
<div className="pb-2.5"> <div className="hidden pb-2.5 sm:block">
<Input <Input
onChange={(e) => onSearch(e.target.value)} onChange={(e) => onSearch(e.target.value)}
prefix={<SearchIcon className="h-4 w-4 text-th-fgd-3" />} prefix={<SearchIcon className="h-4 w-4 text-th-fgd-3" />}
@ -134,7 +134,7 @@ const SwitchMarketDropdown = () => {
<div className=""> <div className="">
<div className="flex justify-between py-1.5"> <div className="flex justify-between py-1.5">
<h4 className="text-xs font-normal">{t('futures')}</h4> <h4 className="text-xs font-normal">{t('futures')}</h4>
<p className="mb-0 text-xs text-th-fgd-3"> <p className="mb-0 hidden text-xs text-th-fgd-3 sm:block">
{t('favorite')} {t('favorite')}
</p> </p>
</div> </div>
@ -148,7 +148,7 @@ const SwitchMarketDropdown = () => {
))} ))}
<div className="flex justify-between py-1.5"> <div className="flex justify-between py-1.5">
<h4 className="text-xs font-normal">{t('spot')}</h4> <h4 className="text-xs font-normal">{t('spot')}</h4>
<p className="mb-0 text-xs text-th-fgd-3"> <p className="mb-0 hidden text-xs text-th-fgd-3 sm:block">
{t('favorite')} {t('favorite')}
</p> </p>
</div> </div>

View File

@ -36,12 +36,12 @@ const BottomBar = () => {
<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-3 py-2.5">
<Link <Link
href={{ href={{
pathname: '/select', pathname: '/markets',
}} }}
> >
<div <div
className={`${ className={`${
asPath === '/select' ? 'text-th-primary' : 'text-th-fgd-3' asPath === '/markets' ? 'text-th-primary' : 'text-th-fgd-3'
} default-transition col-span-1 flex cursor-pointer flex-col items-center hover:text-th-primary`} } default-transition col-span-1 flex cursor-pointer flex-col items-center hover:text-th-primary`}
> >
<BtcMonoIcon className="mb-1 h-4 w-4" /> <BtcMonoIcon className="mb-1 h-4 w-4" />

View File

@ -1,7 +1,7 @@
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { Disclosure } from '@headlessui/react' import { Disclosure } from '@headlessui/react'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { SwitchHorizontalIcon, XIcon } from '@heroicons/react/outline' import { XIcon } from '@heroicons/react/outline'
import useMangoStore from '../../stores/useMangoStore' import useMangoStore from '../../stores/useMangoStore'
import { getWeights, PerpMarket } from '@blockworks-foundation/mango-client' import { getWeights, PerpMarket } from '@blockworks-foundation/mango-client'
import { CandlesIcon } from '../icons' import { CandlesIcon } from '../icons'
@ -16,8 +16,8 @@ import RecentMarketTrades from '../RecentMarketTrades'
import FloatingElement from '../FloatingElement' import FloatingElement from '../FloatingElement'
import Swipeable from './Swipeable' import Swipeable from './Swipeable'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import Link from 'next/link'
import { useWallet } from '@solana/wallet-adapter-react' import { useWallet } from '@solana/wallet-adapter-react'
import SwitchMarketDropdown from 'components/SwitchMarketDropdown'
const TVChartContainer = dynamic( const TVChartContainer = dynamic(
() => import('../../components/TradingView/index'), () => import('../../components/TradingView/index'),
@ -31,9 +31,6 @@ const MobileTradePage = () => {
const selectedMarket = useMangoStore((s) => s.selectedMarket.current) const selectedMarket = useMangoStore((s) => s.selectedMarket.current)
const marketConfig = useMangoStore((s) => s.selectedMarket.config) const marketConfig = useMangoStore((s) => s.selectedMarket.config)
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
const baseSymbol = marketConfig.baseSymbol
const isPerpMarket = marketConfig.kind === 'perp'
const handleChangeViewIndex = (index) => { const handleChangeViewIndex = (index) => {
setViewIndex(index) setViewIndex(index)
@ -57,30 +54,10 @@ const MobileTradePage = () => {
<div className="px-2 pb-14 pt-4"> <div className="px-2 pb-14 pt-4">
<div className="relative"> <div className="relative">
<div className="flex items-center"> <div className="flex items-center">
<img <SwitchMarketDropdown />
alt=""
width="30"
height="30"
src={`/assets/icons/${baseSymbol.toLowerCase()}.svg`}
className="mr-2"
/>
<div className="flex items-center">
<div className="pr-0.5 text-xl font-semibold">{baseSymbol}</div>
<span className="text-xl text-th-fgd-4">
{isPerpMarket ? '-' : '/'}
</span>
<div className="pl-0.5 text-xl font-semibold">
{isPerpMarket ? 'PERP' : groupConfig.quoteSymbol}
</div>
</div>
<span className="ml-2 rounded border border-th-primary px-1 py-0.5 text-xs text-th-primary"> <span className="ml-2 rounded border border-th-primary px-1 py-0.5 text-xs text-th-primary">
{initLeverage}x {initLeverage}x
</span> </span>
<Link href="/select">
<div className="ml-2 flex h-10 w-10 items-center justify-center">
<SwitchHorizontalIcon className="h-5 w-5" />
</div>
</Link>
</div> </div>
<Disclosure> <Disclosure>
{({ open }) => ( {({ open }) => (

View File

@ -15,11 +15,7 @@ import {
InformationCircleIcon, InformationCircleIcon,
} from '@heroicons/react/outline' } from '@heroicons/react/outline'
import { notify } from '../../utils/notifications' import { notify } from '../../utils/notifications'
import { import { calculateTradePrice, getDecimalCount } from '../../utils'
calculateTradePrice,
getDecimalCount,
percentFormat,
} from '../../utils'
import { floorToDecimal } from '../../utils/index' import { floorToDecimal } from '../../utils/index'
import useMangoStore, { Orderbook } from '../../stores/useMangoStore' import useMangoStore, { Orderbook } from '../../stores/useMangoStore'
import Button, { LinkButton } from '../Button' import Button, { LinkButton } from '../Button'
@ -79,7 +75,7 @@ export default function AdvancedTradeForm({
const [spotMargin, setSpotMargin] = useState(defaultSpotMargin) const [spotMargin, setSpotMargin] = useState(defaultSpotMargin)
const [positionSizePercent, setPositionSizePercent] = useState('') const [positionSizePercent, setPositionSizePercent] = useState('')
const [insufficientSol, setInsufficientSol] = useState(false) const [insufficientSol, setInsufficientSol] = useState(false)
const { takerFee, makerFee } = useFees() const { takerFee } = useFees()
const { totalMsrm } = useSrmAccount() const { totalMsrm } = useSrmAccount()
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current) const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
@ -947,7 +943,7 @@ export default function AdvancedTradeForm({
</div> </div>
) : null ) : null
) : null} ) : null}
<div className="flex-wrap sm:flex"> <div className="mt-1 flex-wrap sm:flex">
{isLimitOrder ? ( {isLimitOrder ? (
<div className="flex"> <div className="flex">
<div className="mr-4 mt-3"> <div className="mr-4 mt-3">
@ -989,42 +985,44 @@ export default function AdvancedTradeForm({
auto updating the reduceOnly state when doing a market order: auto updating the reduceOnly state when doing a market order:
&& showReduceOnly(perpAccount?.basePosition.toNumber()) && showReduceOnly(perpAccount?.basePosition.toNumber())
*/} */}
{marketConfig.kind === 'perp' || isLuna ? ( <div className="flex">
<div className="mr-4 mt-3"> {marketConfig.kind === 'perp' || isLuna ? (
<Tooltip <div className="mr-4 mt-3">
className="hidden md:block" <Tooltip
delay={250} className="hidden md:block"
placement="left" delay={250}
content={t('tooltip-reduce')} placement="left"
> content={t('tooltip-reduce')}
<Checkbox
checked={reduceOnly}
onChange={(e) => reduceOnChange(e.target.checked)}
disabled={isTriggerOrder || isLuna}
> >
Reduce Only <Checkbox
</Checkbox> checked={reduceOnly}
</Tooltip> onChange={(e) => reduceOnChange(e.target.checked)}
</div> disabled={isTriggerOrder || isLuna}
) : null} >
{marketConfig.kind === 'perp' && tradeType === 'Limit' ? ( Reduce Only
<div className="mt-3"> </Checkbox>
<Tooltip </Tooltip>
className="hidden md:block" </div>
delay={250} ) : null}
placement="left" {marketConfig.kind === 'perp' && tradeType === 'Limit' ? (
content={t('tooltip-post-and-slide')} <div className="mt-3">
> <Tooltip
<Checkbox className="hidden md:block"
checked={postOnlySlide} delay={250}
onChange={(e) => postOnlySlideOnChange(e.target.checked)} placement="left"
disabled={isTriggerOrder} content={t('tooltip-post-and-slide')}
> >
Slide <Checkbox
</Checkbox> checked={postOnlySlide}
</Tooltip> onChange={(e) => postOnlySlideOnChange(e.target.checked)}
</div> disabled={isTriggerOrder}
) : null} >
Slide
</Checkbox>
</Tooltip>
</div>
) : null}
</div>
{marketConfig.kind === 'spot' ? ( {marketConfig.kind === 'spot' ? (
<div className="mt-3"> <div className="mt-3">
<Tooltip <Tooltip
@ -1050,7 +1048,7 @@ export default function AdvancedTradeForm({
<div className="text-xs">{t('slippage-warning')}</div> <div className="text-xs">{t('slippage-warning')}</div>
</div> </div>
) : null} ) : null}
<div className={`mt-3 flex`}> <div className={`mt-4 flex`}>
{canTrade ? ( {canTrade ? (
<button <button
disabled={disabledTradeButton} disabled={disabledTradeButton}
@ -1174,18 +1172,7 @@ export default function AdvancedTradeForm({
</> </>
)} )}
</div> </div>
) : ( ) : null}
<div className="mt-2.5 flex flex-col items-center justify-center px-6 text-xs text-th-fgd-4 md:flex-row">
<div>
{t('maker-fee')}: {percentFormat.format(makerFee)}{' '}
</div>
<span className="hidden md:block md:px-1">|</span>
<div>
{' '}
{t('taker-fee')}: {percentFormat.format(takerFee)}
</div>
</div>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -6,7 +6,7 @@ import { useTranslation } from 'next-i18next'
import MarketsTable from '../components/MarketsTable' import MarketsTable from '../components/MarketsTable'
import Tabs from '../components/Tabs' import Tabs from '../components/Tabs'
const TABS = ['perp', 'spot'] const TABS = ['futures', 'spot']
export async function getStaticProps({ locale }) { export async function getStaticProps({ locale }) {
return { return {
@ -18,15 +18,13 @@ export async function getStaticProps({ locale }) {
} }
export default function Markets() { export default function Markets() {
const [activeTab, setActiveTab] = useState('perp') const [activeTab, setActiveTab] = useState('futures')
const { t } = useTranslation(['common']) const { t } = useTranslation(['common'])
const handleTabChange = (tabName) => { const handleTabChange = (tabName) => {
setActiveTab(tabName) setActiveTab(tabName)
} }
const isPerp = activeTab === 'perp'
return ( return (
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}> <div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
<TopBar /> <TopBar />
@ -35,13 +33,14 @@ export default function Markets() {
<h1>{t('markets')}</h1> <h1>{t('markets')}</h1>
</div> </div>
<div className="md:rounded-lg md:bg-th-bkg-2 md:p-6"> <div className="md:rounded-lg md:bg-th-bkg-2 md:p-6">
<Tabs activeTab={activeTab} onChange={handleTabChange} tabs={TABS} /> <div className="mb-0 sm:mb-6">
<h2 className="mb-4"> <Tabs
{isPerp activeTab={activeTab}
? `${t('perp')} ${t('markets')}` onChange={handleTabChange}
: `${t('spot')} ${t('markets')}`} tabs={TABS}
</h2> />
<MarketsTable isPerpMarket={activeTab === 'perp'} /> </div>
<MarketsTable isPerpMarket={activeTab === 'futures'} />
</div> </div>
</PageBodyContainer> </PageBodyContainer>
</div> </div>

View File

@ -1,109 +0,0 @@
import { useTranslation } from 'next-i18next'
import { useEffect, useState } from 'react'
import { ChevronRightIcon } from '@heroicons/react/solid'
import useMangoStore from '../stores/useMangoStore'
import Link from 'next/link'
import { formatUsdValue } from '../utils'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import PageBodyContainer from '../components/PageBodyContainer'
import TopBar from '../components/TopBar'
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale, [
'common',
'tv-chart',
'profile',
])),
// Will be passed to the page component as props
},
}
}
const SelectMarket = () => {
const { t } = useTranslation('common')
const groupConfig = useMangoStore((s) => s.selectedMangoGroup.config)
const mangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
const [markets, setMarkets] = useState<any[]>([])
useEffect(() => {
const markets: any[] = []
const allMarkets =
groupConfig?.spotMarkets && groupConfig?.perpMarkets
? [...groupConfig.spotMarkets, ...groupConfig.perpMarkets]
: []
allMarkets.forEach((market) => {
const base = market.name.slice(0, -5)
const found = markets.find((b) => b.baseAsset === base)
if (!found) {
markets.push({ baseAsset: base, markets: [market] })
} else {
found.markets.push(market)
}
})
setMarkets(markets)
}, [])
if (!mangoCache) {
return null
}
return (
<div className={`bg-th-bkg-1 text-th-fgd-1 transition-all`}>
<TopBar />
<PageBodyContainer>
<div className="py-4 text-2xl font-bold text-th-fgd-1">
{t('markets')}
</div>
{markets.map((mkt) => {
return (
<div key={mkt.baseAsset}>
<div className="flex items-center justify-between bg-th-bkg-3 px-2.5 py-2">
<div className="flex items-center">
<img
alt=""
src={`/assets/icons/${mkt.baseAsset.toLowerCase()}.svg`}
className={`mr-2.5 h-5 w-auto`}
/>
<span className="text-th-fgd-2">{mkt.baseAsset}</span>
</div>
</div>
<div className="divide-y divide-th-bkg-4">
{mangoGroup
? mkt.markets.map((m) => (
<div
className={`flex items-center justify-between px-2.5 text-xs`}
key={m.name}
>
<Link href={`/?name=${m.name}`} key={m.name}>
<a className="default-transition flex h-12 w-full cursor-pointer items-center justify-between text-th-fgd-2 hover:text-th-primary">
{m.name}
<div className="flex items-center">
<span className="w-20 text-right">
{formatUsdValue(
mangoGroup
.getPrice(m.marketIndex, mangoCache)
.toNumber()
)}
</span>
<ChevronRightIcon className="ml-1 h-4 w-5 text-th-fgd-2" />
</div>
</a>
</Link>
</div>
))
: null}
</div>
</div>
)
})}
{/* spacer so last market can be selected albeit bottom bar overlay */}
<p className="flex h-12 md:hidden"></p>
</PageBodyContainer>
</div>
)
}
export default SelectMarket

View File

@ -431,6 +431,7 @@
"trigger-price": "Trigger Price", "trigger-price": "Trigger Price",
"try-again": "Try again", "try-again": "Try again",
"type": "Type", "type": "Type",
"unavailable": "Unavailable",
"unrealized-pnl": "Unrealized PnL", "unrealized-pnl": "Unrealized PnL",
"unsettled": "Unsettled", "unsettled": "Unsettled",
"unsettled-balance": "Redeemable Value", "unsettled-balance": "Redeemable Value",

View File

@ -431,6 +431,7 @@
"trigger-price": "Precio de activación", "trigger-price": "Precio de activación",
"try-again": "Inténtalo de nuevo", "try-again": "Inténtalo de nuevo",
"type": "Tipo", "type": "Tipo",
"unavailable": "Unavailable",
"unrealized-pnl": "PnL no realizado", "unrealized-pnl": "PnL no realizado",
"unsettled": "Inestable", "unsettled": "Inestable",
"unsettled-balance": "Saldo pendiente", "unsettled-balance": "Saldo pendiente",

View File

@ -431,6 +431,7 @@
"trigger-price": "触发价格", "trigger-price": "触发价格",
"try-again": "请再试一次", "try-again": "请再试一次",
"type": "类型", "type": "类型",
"unavailable": "无资料",
"unrealized-pnl": "未实现盈亏", "unrealized-pnl": "未实现盈亏",
"unsettled": "未结清", "unsettled": "未结清",
"unsettled-balance": "可领取价值", "unsettled-balance": "可领取价值",

View File

@ -431,6 +431,7 @@
"trigger-price": "觸發價格", "trigger-price": "觸發價格",
"try-again": "請再試一次", "try-again": "請再試一次",
"type": "類型", "type": "類型",
"unavailable": "無資料",
"unrealized-pnl": "未實現盈虧", "unrealized-pnl": "未實現盈虧",
"unsettled": "未結清", "unsettled": "未結清",
"unsettled-balance": "可領取價值", "unsettled-balance": "可領取價值",

View File

@ -40,6 +40,7 @@ import { getProfilePicture, ProfilePicture } from '@solflare-wallet/pfp'
import { decodeBook } from '../hooks/useHydrateStore' import { decodeBook } from '../hooks/useHydrateStore'
import { IOrderLineAdapter } from '../public/charting_library/charting_library' import { IOrderLineAdapter } from '../public/charting_library/charting_library'
import { Wallet } from '@solana/wallet-adapter-react' import { Wallet } from '@solana/wallet-adapter-react'
import { coingeckoIds } from 'utils/tokens'
import { getTokenAccountsByMint } from 'utils/tokens' import { getTokenAccountsByMint } from 'utils/tokens'
import { getParsedNftAccountsByOwner } from 'utils/getParsedNftAccountsByOwner' import { getParsedNftAccountsByOwner } from 'utils/getParsedNftAccountsByOwner'
@ -264,6 +265,7 @@ export type MangoStore = {
deleteAlert: (id: string) => void deleteAlert: (id: string) => void
loadAlerts: (pk: PublicKey) => void loadAlerts: (pk: PublicKey) => void
fetchMarketsInfo: () => void fetchMarketsInfo: () => void
fetchCoingeckoPrices: () => void
} }
alerts: { alerts: {
activeAlerts: Array<Alert> activeAlerts: Array<Alert>
@ -277,6 +279,7 @@ export type MangoStore = {
tradingView: { tradingView: {
orderLines: Map<string, IOrderLineAdapter> orderLines: Map<string, IOrderLineAdapter>
} }
coingeckoPrices: any[]
} }
const useMangoStore = create< const useMangoStore = create<
@ -408,6 +411,7 @@ const useMangoStore = create<
tradingView: { tradingView: {
orderLines: new Map(), orderLines: new Map(),
}, },
coingeckoPrices: [],
set: (fn) => set(produce(fn)), set: (fn) => set(produce(fn)),
actions: { actions: {
async fetchWalletTokens(wallet: Wallet) { async fetchWalletTokens(wallet: Wallet) {
@ -1125,6 +1129,29 @@ const useMangoStore = create<
console.log('ERORR: Unable to load all market info') console.log('ERORR: Unable to load all market info')
} }
}, },
async fetchCoingeckoPrices() {
const set = get().set
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`
).then((res) => res.json())
)
}
const data = await Promise.all(promises)
for (let i = 0; i < data.length; i++) {
data[i].symbol = coingeckoIds[i].symbol
}
set((state) => {
state.coingeckoPrices = data
})
} catch (e) {
console.log('ERORR: Unable to load Coingecko prices')
}
},
}, },
} }
}) })

View File

@ -23,6 +23,23 @@ export function parseTokenAccountData(data: Buffer): {
} }
} }
export const coingeckoIds = [
{ id: 'bitcoin', symbol: 'BTC' },
{ id: 'ethereum', symbol: 'ETH' },
{ id: 'solana', symbol: 'SOL' },
{ id: 'mango-markets', symbol: 'MNGO' },
{ id: 'binancecoin', symbol: 'BNB' },
{ id: 'serum', symbol: 'SRM' },
{ id: 'raydium', symbol: 'RAY' },
{ id: 'ftx-token', symbol: 'FTT' },
{ id: 'avalanche-2', symbol: 'AVAX' },
{ id: 'terra-luna', symbol: 'LUNA' },
{ id: 'cope', symbol: 'COPE' },
{ id: 'cardano', symbol: 'ADA' },
{ id: 'msol', symbol: 'MSOL' },
{ id: 'tether', symbol: 'USDT' },
]
export async function getTokenAccountsByMint( export async function getTokenAccountsByMint(
connection: Connection, connection: Connection,
mint: string mint: string