mango-v4-ui/components/swap/SwapTokenChart.tsx

341 lines
11 KiB
TypeScript
Raw Normal View History

2022-07-23 04:30:50 -07:00
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
2022-07-10 19:01:16 -07:00
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
2022-07-12 20:58:13 -07:00
import {
AreaChart,
Area,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
} from 'recharts'
2022-07-22 10:07:55 -07:00
import FlipNumbers from 'react-flip-numbers'
2022-07-10 19:01:16 -07:00
import LineChartIcon from '../icons/LineChartIcon'
import ContentBox from '../shared/ContentBox'
2022-07-15 13:26:29 -07:00
import { GREEN, RED } from '../../styles/colors'
2022-07-19 20:33:30 -07:00
import { DownTriangle, UpTriangle } from '../shared/DirectionTriangles'
2022-07-22 14:39:02 -07:00
import { formatFixedDecimals } from '../../utils/numbers'
2022-07-10 19:01:16 -07:00
dayjs.extend(relativeTime)
interface SwapTokenChartProps {
inputTokenId?: string
outputTokenId?: string
}
const fetchChartData = async (
baseTokenId: string,
quoteTokenId: string,
daysToShow: number
) => {
const inputResponse = await fetch(
`https://api.coingecko.com/api/v3/coins/${baseTokenId}/ohlc?vs_currency=usd&days=${daysToShow}`
)
const outputResponse = await fetch(
`https://api.coingecko.com/api/v3/coins/${quoteTokenId}/ohlc?vs_currency=usd&days=${daysToShow}`
)
const inputData = await inputResponse.json()
const outputData = await outputResponse.json()
let data: any[] = []
if (Array.isArray(inputData)) {
data = data.concat(inputData)
}
if (Array.isArray(outputData)) {
data = data.concat(outputData)
}
const formattedData = data.reduce((a, c) => {
const found = a.find((price: any) => price.time === c[0])
if (found) {
if (['usd-coin', 'tether'].includes(quoteTokenId)) {
found.price = found.inputPrice / c[4]
} else {
found.price = c[4] / found.inputPrice
}
} else {
a.push({ time: c[0], inputPrice: c[4] })
}
return a
}, [])
formattedData[formattedData.length - 1].time = Date.now()
return formattedData.filter((d: any) => d.price)
}
const fetchTokenInfo = async (tokenId: string) => {
const response = await fetch(
`https://api.coingecko.com/api/v3/coins/${tokenId}?localization=false&tickers=false&developer_data=false&sparkline=false
`
)
const data = await response.json()
return data
}
2022-07-10 19:01:16 -07:00
const SwapTokenChart: FunctionComponent<SwapTokenChartProps> = ({
inputTokenId,
outputTokenId,
}) => {
const [chartData, setChartData] = useState([])
const [baseTokenId, setBaseTokenId] = useState('')
const [quoteTokenId, setQuoteTokenId] = useState('')
const [inputTokenInfo, setInputTokenInfo] = useState<any>(null)
const [outputTokenInfo, setOutputTokenInfo] = useState<any>(null)
const [mouseData, setMouseData] = useState<any>(null)
const [daysToShow, setDaysToShow] = useState(1)
const handleMouseMove = (coords: any) => {
if (coords.activePayload) {
setMouseData(coords.activePayload[0].payload)
}
}
const handleMouseLeave = () => {
setMouseData(null)
}
useEffect(() => {
if (!inputTokenId || !outputTokenId) return
2022-07-10 19:01:16 -07:00
if (['usd-coin', 'tether'].includes(inputTokenId)) {
setBaseTokenId(outputTokenId)
setQuoteTokenId(inputTokenId)
} else {
setBaseTokenId(inputTokenId)
setQuoteTokenId(outputTokenId)
}
}, [inputTokenId, outputTokenId])
// Use ohlc data
const getChartData = useCallback(async () => {
if (!baseTokenId || !quoteTokenId) return
const chartData = await fetchChartData(
baseTokenId,
quoteTokenId,
daysToShow
2022-07-10 19:01:16 -07:00
)
setChartData(chartData)
}, [baseTokenId, quoteTokenId, daysToShow])
2022-07-10 19:01:16 -07:00
const getInputTokenInfo = useCallback(async () => {
if (!inputTokenId) return
const response = await fetchTokenInfo(inputTokenId)
setInputTokenInfo(response)
}, [inputTokenId])
2022-07-10 19:01:16 -07:00
const getOutputTokenInfo = useCallback(async () => {
if (!outputTokenId) return
const response = await fetchTokenInfo(outputTokenId)
setOutputTokenInfo(response)
}, [outputTokenId])
2022-07-10 19:01:16 -07:00
useEffect(() => {
getChartData()
}, [getChartData])
2022-07-10 19:01:16 -07:00
useEffect(() => {
getInputTokenInfo()
getOutputTokenInfo()
}, [getInputTokenInfo, getOutputTokenInfo])
2022-07-10 19:01:16 -07:00
2022-07-19 20:49:33 -07:00
const calculateChartChange = () => {
if (chartData.length) {
2022-07-22 10:07:55 -07:00
// if (mouseData) {
// const index = chartData.findIndex((d: any) => d.time === mouseData.time)
// return (
// ((chartData[chartData.length - 1]['price'] -
// chartData[index]['price']) /
// chartData[0]['price']) *
// 100
// )
// } else
return (
((chartData[chartData.length - 1]['price'] - chartData[0]['price']) /
chartData[0]['price']) *
100
)
2022-07-19 20:49:33 -07:00
}
return 0
}
2022-07-10 19:01:16 -07:00
return (
2022-07-14 18:46:34 -07:00
<ContentBox hideBorder hidePadding>
2022-07-10 19:01:16 -07:00
{chartData.length && baseTokenId && quoteTokenId ? (
2022-07-19 20:33:30 -07:00
<div className="relative flex justify-between md:block">
2022-07-10 19:01:16 -07:00
<div className="flex items-start justify-between">
<div>
{inputTokenInfo && outputTokenInfo ? (
2022-07-15 05:50:29 -07:00
<p className="mb-0.5 text-base text-th-fgd-3">
{['usd-coin', 'tether'].includes(inputTokenId || '')
? `${outputTokenInfo?.symbol?.toUpperCase()}/${inputTokenInfo?.symbol?.toUpperCase()}`
: `${inputTokenInfo?.symbol?.toUpperCase()}/${outputTokenInfo?.symbol?.toUpperCase()}`}
2022-07-15 05:50:29 -07:00
</p>
2022-07-10 19:01:16 -07:00
) : null}
{mouseData ? (
<>
2022-07-19 20:33:30 -07:00
<div className="mb-0.5 flex flex-col text-4xl font-bold text-th-fgd-1 md:flex-row md:items-end">
2022-07-22 10:07:55 -07:00
<FlipNumbers
height={36}
width={24}
play
delay={0.025}
duration={1}
2022-07-22 14:39:02 -07:00
numbers={formatFixedDecimals(mouseData['price'])}
2022-07-22 10:07:55 -07:00
/>
2022-07-10 19:01:16 -07:00
<span
2022-07-19 20:49:33 -07:00
className={`ml-0 mt-2 flex items-center text-sm md:ml-3 md:mt-0 ${
calculateChartChange() >= 0
? 'text-th-green'
: 'text-th-red'
2022-07-10 19:01:16 -07:00
}`}
>
2022-07-19 20:49:33 -07:00
{calculateChartChange() >= 0 ? (
<UpTriangle />
) : (
<DownTriangle />
)}
<span className="ml-1">
{calculateChartChange().toFixed(2)}%
</span>
2022-07-10 19:01:16 -07:00
</span>
</div>
2022-07-15 05:50:29 -07:00
<p className="text-th-fgd-4">
2022-07-10 19:01:16 -07:00
{dayjs(mouseData['time']).format('DD MMM YY, h:mma')}
2022-07-15 05:50:29 -07:00
</p>
2022-07-10 19:01:16 -07:00
</>
) : (
<>
2022-07-19 20:33:30 -07:00
<div className="mb-0.5 flex flex-col text-4xl font-bold text-th-fgd-1 md:flex-row md:items-end">
2022-07-22 10:07:55 -07:00
<FlipNumbers
height={36}
width={24}
play
2022-07-22 14:39:02 -07:00
numbers={formatFixedDecimals(
2022-07-22 10:07:55 -07:00
chartData[chartData.length - 1]['price']
)}
/>
2022-07-10 19:01:16 -07:00
<span
2022-07-19 20:49:33 -07:00
className={`ml-0 mt-2 flex items-center text-sm md:ml-3 md:mt-0 ${
calculateChartChange() >= 0
? 'text-th-green'
: 'text-th-red'
2022-07-10 19:01:16 -07:00
}`}
>
2022-07-19 20:49:33 -07:00
{calculateChartChange() >= 0 ? (
<UpTriangle />
) : (
<DownTriangle />
)}
<span className="ml-1">
{calculateChartChange().toFixed(2)}%
</span>
2022-07-10 19:01:16 -07:00
</span>
</div>
2022-07-15 05:50:29 -07:00
<p className="text-th-fgd-4">
2022-07-10 19:01:16 -07:00
{dayjs(chartData[chartData.length - 1]['time']).format(
'DD MMM YY, h:mma'
)}
2022-07-15 05:50:29 -07:00
</p>
2022-07-10 19:01:16 -07:00
</>
)}
</div>
2022-07-19 20:33:30 -07:00
</div>
<div className="-mt-1 h-28 w-1/2 md:h-72 md:w-auto">
<div className="-mb-2 flex justify-end md:absolute md:-top-1 md:right-0">
2022-07-10 19:01:16 -07:00
<button
2022-07-20 21:50:56 -07:00
className={`rounded-md px-3 py-2 font-bold text-th-fgd-4 focus:outline-none md:hover:text-th-primary ${
2022-07-10 19:01:16 -07:00
daysToShow === 1 && 'text-th-primary'
}`}
onClick={() => setDaysToShow(1)}
>
24H
</button>
<button
2022-07-20 21:50:56 -07:00
className={`rounded-md px-3 py-2 font-bold text-th-fgd-4 focus:outline-none md:hover:text-th-primary ${
2022-07-10 19:01:16 -07:00
daysToShow === 7 && 'text-th-primary'
}`}
onClick={() => setDaysToShow(7)}
>
7D
</button>
<button
2022-07-20 21:50:56 -07:00
className={`rounded-md px-3 py-2 font-bold text-th-fgd-4 focus:outline-none md:hover:text-th-primary ${
2022-07-10 19:01:16 -07:00
daysToShow === 30 && 'text-th-primary'
}`}
onClick={() => setDaysToShow(30)}
>
30D
</button>
</div>
2022-07-19 20:33:30 -07:00
<div className="-mx-6 h-full">
2022-07-12 20:58:13 -07:00
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={chartData}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
<Tooltip
cursor={{
strokeOpacity: 0.09,
}}
content={<></>}
/>
<defs>
<linearGradient
id="gradientArea"
x1="0"
y1="0"
x2="0"
y2="1"
>
2022-07-15 13:26:29 -07:00
<stop
offset="0%"
2022-07-19 20:49:33 -07:00
stopColor={calculateChartChange() >= 0 ? GREEN : RED}
2022-07-19 20:33:30 -07:00
stopOpacity={0.25}
2022-07-15 13:26:29 -07:00
/>
<stop
offset="99%"
2022-07-19 20:49:33 -07:00
stopColor={calculateChartChange() >= 0 ? GREEN : RED}
2022-07-15 13:26:29 -07:00
stopOpacity={0}
/>
</linearGradient>
</defs>
2022-07-12 20:58:13 -07:00
<Area
isAnimationActive={true}
type="monotone"
dataKey="price"
2022-07-19 20:49:33 -07:00
stroke={calculateChartChange() >= 0 ? GREEN : RED}
2022-07-18 20:58:21 -07:00
strokeWidth={1.5}
2022-07-12 20:58:13 -07:00
fill="url(#gradientArea)"
/>
<XAxis
dataKey="time"
hide
padding={{ left: 20, right: 20 }}
/>
<YAxis
dataKey="price"
type="number"
domain={['dataMin', 'dataMax']}
hide
padding={{ top: 20, bottom: 20 }}
/>
</AreaChart>
</ResponsiveContainer>
2022-07-10 19:01:16 -07:00
</div>
2022-07-19 20:33:30 -07:00
</div>
2022-07-10 19:01:16 -07:00
</div>
) : (
2022-07-22 10:07:55 -07:00
<div className="mt-4 flex h-96 items-center justify-center rounded-lg bg-th-bkg-2 p-4 text-th-fgd-3 md:mt-0">
<div className="">
<LineChartIcon className="mx-auto h-12 w-12 text-th-primary" />
<span className="text-lg">Chart not available</span>
</div>
2022-07-10 19:01:16 -07:00
</div>
)}
</ContentBox>
)
}
export default SwapTokenChart