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-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-23 21:27:54 -07:00
|
|
|
import SheenLoader from '../shared/SheenLoader'
|
2022-08-02 17:17:42 -07:00
|
|
|
import { COLORS } from '../../styles/colors'
|
|
|
|
import { useTheme } from 'next-themes'
|
2022-07-10 19:01:16 -07:00
|
|
|
|
|
|
|
dayjs.extend(relativeTime)
|
|
|
|
|
|
|
|
interface SwapTokenChartProps {
|
|
|
|
inputTokenId?: string
|
|
|
|
outputTokenId?: string
|
|
|
|
}
|
|
|
|
|
2022-07-22 11:18:19 -07:00
|
|
|
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([])
|
2022-07-23 21:27:54 -07:00
|
|
|
const [loadChartData, setLoadChartData] = useState(true)
|
2022-07-10 19:01:16 -07:00
|
|
|
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)
|
2022-08-02 17:17:42 -07:00
|
|
|
const { theme } = useTheme()
|
2022-07-10 19:01:16 -07:00
|
|
|
|
|
|
|
const handleMouseMove = (coords: any) => {
|
|
|
|
if (coords.activePayload) {
|
|
|
|
setMouseData(coords.activePayload[0].payload)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleMouseLeave = () => {
|
|
|
|
setMouseData(null)
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
2022-07-22 11:18:19 -07:00
|
|
|
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
|
2022-07-22 11:18:19 -07:00
|
|
|
const getChartData = useCallback(async () => {
|
|
|
|
if (!baseTokenId || !quoteTokenId) return
|
2022-08-09 15:44:01 -07:00
|
|
|
try {
|
|
|
|
const chartData = await fetchChartData(
|
|
|
|
baseTokenId,
|
|
|
|
quoteTokenId,
|
|
|
|
daysToShow
|
|
|
|
)
|
|
|
|
setChartData(chartData)
|
|
|
|
setLoadChartData(false)
|
|
|
|
} catch (e) {
|
|
|
|
console.log('Unable to load chart data')
|
|
|
|
}
|
2022-07-22 11:18:19 -07:00
|
|
|
}, [baseTokenId, quoteTokenId, daysToShow])
|
2022-07-10 19:01:16 -07:00
|
|
|
|
2022-07-22 11:18:19 -07:00
|
|
|
const getInputTokenInfo = useCallback(async () => {
|
|
|
|
if (!inputTokenId) return
|
2022-08-09 15:44:01 -07:00
|
|
|
try {
|
|
|
|
const response = await fetchTokenInfo(inputTokenId)
|
|
|
|
setInputTokenInfo(response)
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e)
|
|
|
|
}
|
2022-07-22 11:18:19 -07:00
|
|
|
}, [inputTokenId])
|
2022-07-10 19:01:16 -07:00
|
|
|
|
2022-07-22 11:18:19 -07:00
|
|
|
const getOutputTokenInfo = useCallback(async () => {
|
|
|
|
if (!outputTokenId) return
|
2022-08-09 15:44:01 -07:00
|
|
|
try {
|
|
|
|
const response = await fetchTokenInfo(outputTokenId)
|
|
|
|
setOutputTokenInfo(response)
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e)
|
|
|
|
}
|
2022-07-22 11:18:19 -07:00
|
|
|
}, [outputTokenId])
|
2022-07-10 19:01:16 -07:00
|
|
|
|
2022-07-22 11:18:19 -07:00
|
|
|
useEffect(() => {
|
|
|
|
getChartData()
|
|
|
|
}, [getChartData])
|
2022-07-10 19:01:16 -07:00
|
|
|
|
2022-07-22 11:18:19 -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-26 04:15:07 -07:00
|
|
|
if (mouseData) {
|
|
|
|
const index = chartData.findIndex((d: any) => d.time === mouseData.time)
|
|
|
|
return (
|
2022-07-29 10:57:12 -07:00
|
|
|
((chartData[index]['price'] - chartData[0]['price']) /
|
2022-07-26 04:15:07 -07:00
|
|
|
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-23 21:27:54 -07:00
|
|
|
{loadChartData ? (
|
|
|
|
<SheenLoader>
|
2022-07-24 04:48:55 -07:00
|
|
|
<div className="h-[448px] rounded-lg bg-th-bkg-2" />
|
2022-07-23 21:27:54 -07:00
|
|
|
</SheenLoader>
|
|
|
|
) : 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">
|
2022-07-14 12:33:11 -07:00
|
|
|
{['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-24 18:47:12 -07:00
|
|
|
<div className="mb-1 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
|
2022-07-23 22:45:11 -07:00
|
|
|
height={40}
|
|
|
|
width={26}
|
2022-07-22 10:07:55 -07:00
|
|
|
play
|
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-24 18:47:12 -07:00
|
|
|
<p className="text-sm 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-24 18:47:12 -07:00
|
|
|
<div className="mb-1 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
|
2022-07-23 22:45:11 -07:00
|
|
|
height={40}
|
|
|
|
width={26}
|
2022-07-22 10:07:55 -07:00
|
|
|
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-24 18:47:12 -07:00
|
|
|
<p className="text-sm 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">
|
2022-07-22 11:18:19 -07:00
|
|
|
<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-08-09 10:06:36 -07:00
|
|
|
className={`rounded-md px-3 py-2 font-bold focus:outline-none md:hover:text-th-primary ${
|
|
|
|
daysToShow === 1 ? 'text-th-primary' : 'text-th-fgd-4'
|
2022-07-10 19:01:16 -07:00
|
|
|
}`}
|
|
|
|
onClick={() => setDaysToShow(1)}
|
|
|
|
>
|
|
|
|
24H
|
|
|
|
</button>
|
|
|
|
<button
|
2022-08-09 10:06:36 -07:00
|
|
|
className={`rounded-md px-3 py-2 font-bold focus:outline-none md:hover:text-th-primary ${
|
|
|
|
daysToShow === 7 ? 'text-th-primary' : 'text-th-fgd-4'
|
2022-07-10 19:01:16 -07:00
|
|
|
}`}
|
|
|
|
onClick={() => setDaysToShow(7)}
|
|
|
|
>
|
|
|
|
7D
|
|
|
|
</button>
|
|
|
|
<button
|
2022-08-09 10:06:36 -07:00
|
|
|
className={`rounded-md px-3 py-2 font-bold focus:outline-none md:hover:text-th-primary ${
|
|
|
|
daysToShow === 30 ? 'text-th-primary' : 'text-th-fgd-4'
|
2022-07-10 19:01:16 -07:00
|
|
|
}`}
|
|
|
|
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={<></>}
|
|
|
|
/>
|
2022-07-14 12:33:11 -07:00
|
|
|
<defs>
|
|
|
|
<linearGradient
|
|
|
|
id="gradientArea"
|
|
|
|
x1="0"
|
|
|
|
y1="0"
|
|
|
|
x2="0"
|
|
|
|
y2="1"
|
|
|
|
>
|
2022-07-15 13:26:29 -07:00
|
|
|
<stop
|
|
|
|
offset="0%"
|
2022-08-02 17:17:42 -07:00
|
|
|
stopColor={
|
|
|
|
calculateChartChange() >= 0
|
|
|
|
? COLORS.GREEN[theme]
|
|
|
|
: COLORS.RED[theme]
|
|
|
|
}
|
2022-07-22 14:39:02 -07:00
|
|
|
stopOpacity={0.15}
|
2022-07-15 13:26:29 -07:00
|
|
|
/>
|
|
|
|
<stop
|
|
|
|
offset="99%"
|
2022-08-02 17:17:42 -07:00
|
|
|
stopColor={
|
|
|
|
calculateChartChange() >= 0
|
|
|
|
? COLORS.GREEN[theme]
|
|
|
|
: COLORS.RED[theme]
|
|
|
|
}
|
2022-07-15 13:26:29 -07:00
|
|
|
stopOpacity={0}
|
|
|
|
/>
|
2022-07-14 12:33:11 -07:00
|
|
|
</linearGradient>
|
|
|
|
</defs>
|
2022-07-12 20:58:13 -07:00
|
|
|
<Area
|
2022-08-01 08:53:09 -07:00
|
|
|
isAnimationActive={false}
|
2022-07-12 20:58:13 -07:00
|
|
|
type="monotone"
|
|
|
|
dataKey="price"
|
2022-08-02 17:17:42 -07:00
|
|
|
stroke={
|
|
|
|
calculateChartChange() >= 0
|
|
|
|
? COLORS.GREEN[theme]
|
|
|
|
: COLORS.RED[theme]
|
|
|
|
}
|
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="">
|
2022-07-23 21:27:54 -07:00
|
|
|
<LineChartIcon className="mx-auto h-10 w-10 text-th-fgd-4" />
|
|
|
|
<p className="text-th-fgd-4">Chart not available</p>
|
2022-07-22 10:07:55 -07:00
|
|
|
</div>
|
2022-07-10 19:01:16 -07:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</ContentBox>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default SwapTokenChart
|