mango-ui-v3/components/SwapTokenInsights.tsx

393 lines
16 KiB
TypeScript
Raw Normal View History

2022-01-09 21:21:05 -08:00
import { useEffect, useState } from 'react'
import { PublicKey } from '@solana/web3.js'
2022-01-10 16:54:50 -08:00
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
2022-01-09 21:21:05 -08:00
import ButtonGroup from './ButtonGroup'
2022-01-10 16:54:50 -08:00
import { numberCompacter, numberFormatter } from './SwapTokenInfo'
2022-01-10 21:14:57 -08:00
import Button, { IconButton } from './Button'
2022-01-10 18:40:20 -08:00
import Input from './Input'
2022-01-10 21:14:57 -08:00
import { SearchIcon, XIcon } from '@heroicons/react/outline'
import { useTranslation } from 'next-i18next'
2022-02-23 17:37:31 -08:00
import { ExpandableRow } from './TableElements'
const filterByVals = ['change-percent', '24h-volume']
const timeFrameVals = ['24h', '7d', '30d']
const insightTypeVals = ['best', 'worst']
2022-01-10 16:54:50 -08:00
dayjs.extend(relativeTime)
2022-01-09 21:21:05 -08:00
const SwapTokenInsights = ({ formState, jupiterTokens, setOutputToken }) => {
2022-03-25 09:00:48 -07:00
// FIXME: Add correct type for
const [tokenInsights, setTokenInsights] = useState<any>([])
const [filteredTokenInsights, setFilteredTokenInsights] = useState<any>([])
const [insightType, setInsightType] = useState(insightTypeVals[0])
const [filterBy, setFilterBy] = useState(filterByVals[0])
const [timeframe, setTimeframe] = useState(timeFrameVals[0])
2022-01-10 18:40:20 -08:00
const [textFilter, setTextFilter] = useState('')
2022-01-10 21:14:57 -08:00
const [showSearch, setShowSearch] = useState(false)
2022-01-09 21:21:05 -08:00
const [loading, setLoading] = useState(false)
const { t } = useTranslation(['common', 'swap'])
2022-01-09 21:21:05 -08:00
const getTokenInsights = async () => {
setLoading(true)
const ids = jupiterTokens
.filter((token) => token?.extensions?.coingeckoId)
.map((token) => token.extensions.coingeckoId)
const response = await fetch(
2022-01-10 21:14:57 -08:00
`https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=${ids.toString()}&order=market_cap_desc&sparkline=false&price_change_percentage=24h,7d,30d`
2022-01-09 21:21:05 -08:00
)
const data = await response.json()
setLoading(false)
setTokenInsights(data)
}
useEffect(() => {
if (filterBy === filterByVals[0] && textFilter === '') {
//filter by 'change %'
2022-01-09 21:21:05 -08:00
setFilteredTokenInsights(
tokenInsights
2022-01-10 14:59:12 -08:00
.sort((a, b) =>
insightType === insightTypeVals[0] //insight type 'best'
2022-01-10 21:14:57 -08:00
? b[`price_change_percentage_${timeframe}_in_currency`] -
a[`price_change_percentage_${timeframe}_in_currency`]
: a[`price_change_percentage_${timeframe}_in_currency`] -
b[`price_change_percentage_${timeframe}_in_currency`]
2022-01-09 21:21:05 -08:00
)
.slice(0, 10)
)
2022-01-10 18:40:20 -08:00
}
if (filterBy === filterByVals[1] && textFilter === '') {
//filter by 24h vol
2022-01-09 21:21:05 -08:00
setFilteredTokenInsights(
tokenInsights
2022-01-10 14:59:12 -08:00
.sort((a, b) =>
insightType === insightTypeVals[0] //insight type 'best'
2022-01-10 14:59:12 -08:00
? b.total_volume - a.total_volume
: a.total_volume - b.total_volume
2022-01-09 21:21:05 -08:00
)
.slice(0, 10)
)
}
2022-01-10 18:40:20 -08:00
if (textFilter !== '') {
setFilteredTokenInsights(
tokenInsights.filter(
(token) =>
token.name.includes(textFilter) || token.symbol.includes(textFilter)
)
)
}
2022-01-10 21:14:57 -08:00
}, [filterBy, insightType, textFilter, timeframe, tokenInsights])
2022-01-10 14:59:12 -08:00
2022-01-09 21:21:05 -08:00
useEffect(() => {
if (jupiterTokens) {
getTokenInsights()
}
}, [])
2022-01-10 21:14:57 -08:00
const handleToggleSearch = () => {
setShowSearch(!showSearch)
setTextFilter('')
}
2022-01-09 21:21:05 -08:00
return filteredTokenInsights ? (
<div>
<div className="mb-3 flex items-end space-x-2">
2022-01-10 21:14:57 -08:00
{!showSearch ? (
<div className="flex w-full flex-col lg:flex-row lg:items-center lg:justify-between">
<div className="mb-2 w-44 lg:mb-0">
2022-01-10 21:14:57 -08:00
<ButtonGroup
activeValue={filterBy}
className="h-10"
onChange={(t) => setFilterBy(t)}
values={filterByVals}
names={filterByVals.map((val) => t(`swap:${val}`))}
2022-01-10 21:14:57 -08:00
/>
</div>
<div className="flex space-x-2">
{filterBy === filterByVals[0] ? ( //filter by change %
2022-01-10 21:14:57 -08:00
<div className="w-36">
<ButtonGroup
activeValue={timeframe}
className="h-10"
onChange={(t) => setTimeframe(t)}
values={timeFrameVals}
2022-01-10 21:14:57 -08:00
/>
</div>
) : null}
<div className="w-28">
<ButtonGroup
activeValue={insightType}
className="h-10"
onChange={(t) => setInsightType(t)}
values={insightTypeVals}
names={insightTypeVals.map((val) => t(`swap:${val}`))}
2022-01-10 21:14:57 -08:00
/>
</div>
</div>
2022-01-10 18:40:20 -08:00
</div>
2022-01-10 21:14:57 -08:00
) : (
<div className="w-full">
<Input
type="text"
placeholder="Search tokens..."
value={textFilter}
onChange={(e) => setTextFilter(e.target.value)}
prefix={<SearchIcon className="h-4 w-4 text-th-fgd-3" />}
2022-01-10 18:40:20 -08:00
/>
</div>
2022-01-10 21:14:57 -08:00
)}
<IconButton
className="h-10 w-10 flex-shrink-0"
2022-01-10 21:14:57 -08:00
onClick={() => handleToggleSearch()}
>
{showSearch ? (
<XIcon className="h-4 w-4" />
) : (
<SearchIcon className="h-4 w-4" />
)}
</IconButton>
</div>
2022-01-09 21:21:05 -08:00
{loading ? (
<div className="space-y-2">
<div className="h-12 w-full animate-pulse rounded-md bg-th-bkg-3" />
<div className="h-12 w-full animate-pulse rounded-md bg-th-bkg-3" />
<div className="h-12 w-full animate-pulse rounded-md bg-th-bkg-3" />
2022-01-09 21:21:05 -08:00
</div>
2022-01-10 18:40:20 -08:00
) : filteredTokenInsights.length > 0 ? (
2022-02-23 17:37:31 -08:00
<div className="border-b border-th-bkg-4">
{filteredTokenInsights.map((insight) => {
2022-03-25 09:00:48 -07:00
if (!insight) {
return null
}
2022-02-23 17:37:31 -08:00
const jupToken = jupiterTokens.find(
(t) => t?.extensions?.coingeckoId === insight.id
)
return (
<>
<ExpandableRow
buttonTemplate={
<div className="flex w-full items-center">
<div className="flex w-1/2 items-center space-x-3">
2022-01-10 16:54:50 -08:00
<div
className={`min-w-[48px] text-xs ${
timeframe === timeFrameVals[0] //timeframe 24h
2022-01-10 21:14:57 -08:00
? insight.price_change_percentage_24h_in_currency >=
0
? 'text-th-green'
: 'text-th-red'
: timeframe === timeFrameVals[1] //timeframe 7d
2022-01-10 21:14:57 -08:00
? insight.price_change_percentage_7d_in_currency >=
0
? 'text-th-green'
: 'text-th-red'
: insight.price_change_percentage_30d_in_currency >=
0
2022-01-10 16:54:50 -08:00
? 'text-th-green'
: 'text-th-red'
}`}
>
{timeframe === timeFrameVals[0] //timeframe 24h
2022-01-10 21:14:57 -08:00
? insight.price_change_percentage_24h_in_currency
? `${insight.price_change_percentage_24h_in_currency.toFixed(
1
)}%`
: '?'
: timeframe === timeFrameVals[1] //timeframe 7d
2022-01-10 21:14:57 -08:00
? insight.price_change_percentage_7d_in_currency
? `${insight.price_change_percentage_7d_in_currency.toFixed(
1
)}%`
: '?'
: insight.price_change_percentage_30d_in_currency
? `${insight.price_change_percentage_30d_in_currency.toFixed(
2022-01-10 16:54:50 -08:00
1
)}%`
: '?'}
</div>
{insight.image ? (
<img
src={insight.image}
width="24"
height="24"
alt={insight.name}
className="hidden rounded-full lg:block"
2022-01-10 16:54:50 -08:00
/>
) : (
<div className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-th-bkg-3 text-xs text-th-fgd-3">
2022-01-10 16:54:50 -08:00
?
</div>
)}
<div className="text-left">
<div className="font-bold">
{insight?.symbol?.toUpperCase()}
2022-01-10 16:54:50 -08:00
</div>
<div className="text-xs text-th-fgd-3">
2022-01-10 16:54:50 -08:00
{insight.name}
</div>
</div>
</div>
<div className="flex w-1/2 items-center justify-end space-x-3 pl-2 text-right text-xs">
2022-01-10 16:54:50 -08:00
<div>
<div className="mb-[4px] text-th-fgd-4">
{t('price')}
</div>
2022-01-10 16:54:50 -08:00
<div className="text-th-fgd-3">
$
{insight.current_price.toLocaleString(undefined, {
minimumFractionDigits: 2,
2022-01-10 18:40:20 -08:00
maximumFractionDigits: 6,
2022-01-10 16:54:50 -08:00
})}
</div>
</div>
<div className="border-l border-th-bkg-4" />
<div>
<div className="mb-[4px] text-th-fgd-4">
{t('swap:24h-vol')}
</div>
2022-01-10 16:54:50 -08:00
<div className="text-th-fgd-3">
{insight.total_volume > 0
? `$${numberCompacter.format(
insight.total_volume
)}`
: '?'}
</div>
</div>
</div>
2022-02-23 17:37:31 -08:00
<Button
className="ml-3 hidden pl-3 pr-3 text-xs lg:block"
2022-02-23 17:37:31 -08:00
onClick={() =>
setOutputToken({
...formState,
outputMint: new PublicKey(jupToken.address),
})
}
>
{t('buy')}
</Button>
</div>
}
panelTemplate={
<div className="grid grid-flow-row grid-cols-1 sm:grid-cols-2 xl:grid-cols-3">
2022-01-10 16:54:50 -08:00
{insight.market_cap_rank ? (
<div className="m-1 rounded-md border border-th-bkg-4 p-3">
<div className="text-xs text-th-fgd-3">
{t('swap:market-cap-rank')}
2022-01-10 16:54:50 -08:00
</div>
<div className="font-bold text-th-fgd-1">
#{insight.market_cap_rank}
</div>
</div>
) : null}
{insight?.market_cap && insight?.market_cap !== 0 ? (
<div className="m-1 rounded-md border border-th-bkg-4 p-3">
<div className="text-xs text-th-fgd-3">
{t('swap:market-cap')}
2022-01-10 16:54:50 -08:00
</div>
<div className="font-bold text-th-fgd-1">
${numberCompacter.format(insight.market_cap)}
</div>
</div>
) : null}
{insight?.circulating_supply ? (
<div className="m-1 rounded-md border border-th-bkg-4 p-3">
<div className="text-xs text-th-fgd-3">
{t('swap:token-supply')}
2022-01-10 16:54:50 -08:00
</div>
<div className="font-bold text-th-fgd-1">
{numberCompacter.format(insight.circulating_supply)}
</div>
{insight?.max_supply ? (
<div className="text-xs text-th-fgd-2">
{t('swap:max-supply')}
{': '}
2022-01-10 16:54:50 -08:00
{numberCompacter.format(insight.max_supply)}
</div>
) : null}
</div>
) : null}
{insight?.ath ? (
<div className="m-1 rounded-md border border-th-bkg-4 p-3">
<div className="text-xs text-th-fgd-3">
{t('swap:ath')}
2022-01-10 16:54:50 -08:00
</div>
<div className="flex">
<div className="font-bold text-th-fgd-1">
${numberFormatter.format(insight.ath)}
</div>
{insight?.ath_change_percentage ? (
<div
className={`ml-1.5 mt-0.5 text-xs ${
insight?.ath_change_percentage >= 0
? 'text-th-green'
: 'text-th-red'
}`}
>
{insight.ath_change_percentage.toFixed(2)}%
</div>
) : null}
</div>
{insight?.ath_date ? (
<div className="text-xs text-th-fgd-2">
2022-01-10 16:54:50 -08:00
{dayjs(insight.ath_date).fromNow()}
</div>
) : null}
</div>
) : null}
{insight?.atl ? (
<div className="m-1 rounded-md border border-th-bkg-4 p-3">
<div className="text-xs text-th-fgd-3">
{t('swap:atl')}
2022-01-10 16:54:50 -08:00
</div>
<div className="flex">
<div className="font-bold text-th-fgd-1">
${numberFormatter.format(insight.atl)}
</div>
{insight?.atl_change_percentage ? (
<div
className={`ml-1.5 mt-0.5 text-xs ${
insight?.atl_change_percentage >= 0
? 'text-th-green'
: 'text-th-red'
}`}
>
{(insight?.atl_change_percentage).toLocaleString(
undefined,
{
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}
)}
%
</div>
) : null}
</div>
{insight?.atl_date ? (
<div className="text-xs text-th-fgd-2">
2022-01-10 16:54:50 -08:00
{dayjs(insight.atl_date).fromNow()}
</div>
) : null}
</div>
) : null}
</div>
2022-02-23 17:37:31 -08:00
}
/>
</>
)
})}
</div>
2022-01-10 18:40:20 -08:00
) : (
<div className="mt-3 rounded-md bg-th-bkg-3 p-4 text-center text-th-fgd-3">
{t('swap:no-tokens-found')}
2022-01-10 18:40:20 -08:00
</div>
2022-01-09 21:21:05 -08:00
)}
</div>
) : (
<div className="mt-3 rounded-md bg-th-bkg-3 p-4 text-center text-th-fgd-3">
{t('swap:insights-not-available')}
2022-01-09 21:21:05 -08:00
</div>
)
}
export default SwapTokenInsights