2023-07-20 17:45:03 -07:00
|
|
|
import {
|
|
|
|
MouseEventHandler,
|
|
|
|
useCallback,
|
|
|
|
useEffect,
|
|
|
|
useMemo,
|
|
|
|
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,
|
2023-04-13 22:22:39 -07:00
|
|
|
Tooltip as RechartsTooltip,
|
2022-07-12 20:58:13 -07:00
|
|
|
ResponsiveContainer,
|
2022-09-14 12:38:43 -07:00
|
|
|
Text,
|
2023-04-13 22:22:39 -07:00
|
|
|
ReferenceDot,
|
2023-04-15 06:56:50 -07:00
|
|
|
ReferenceDotProps,
|
2022-07-12 20:58:13 -07:00
|
|
|
} 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 ContentBox from '../shared/ContentBox'
|
2023-07-19 18:26:13 -07:00
|
|
|
import { formatCurrencyValue, formatNumericValue } 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'
|
2022-10-05 22:11:28 -07:00
|
|
|
import Change from '../shared/Change'
|
2022-09-07 23:25:32 -07:00
|
|
|
import ChartRangeButtons from '../shared/ChartRangeButtons'
|
2022-09-14 12:38:43 -07:00
|
|
|
import { useViewport } from 'hooks/useViewport'
|
2022-10-05 19:23:31 -07:00
|
|
|
import { formatTokenSymbol } from 'utils/tokens'
|
2022-10-07 16:39:06 -07:00
|
|
|
import { useQuery } from '@tanstack/react-query'
|
2023-02-23 09:28:09 -08:00
|
|
|
import { ChartDataItem, fetchChartData } from 'apis/coingecko'
|
2022-10-10 10:28:53 -07:00
|
|
|
import mangoStore from '@store/mangoStore'
|
2022-11-18 11:11:06 -08:00
|
|
|
import useJupiterSwapData from './useJupiterSwapData'
|
2022-11-23 18:30:20 -08:00
|
|
|
import useLocalStorageState from 'hooks/useLocalStorageState'
|
2023-08-03 03:34:57 -07:00
|
|
|
import {
|
|
|
|
ANIMATION_SETTINGS_KEY,
|
|
|
|
SWAP_CHART_SETTINGS_KEY,
|
|
|
|
} from 'utils/constants'
|
2022-11-24 18:39:14 -08:00
|
|
|
import { INITIAL_ANIMATION_SETTINGS } from '@components/settings/AnimationSettings'
|
2022-12-11 15:21:40 -08:00
|
|
|
import { useTranslation } from 'next-i18next'
|
2023-04-13 22:22:39 -07:00
|
|
|
import {
|
|
|
|
ArrowsRightLeftIcon,
|
|
|
|
EyeIcon,
|
|
|
|
EyeSlashIcon,
|
|
|
|
NoSymbolIcon,
|
|
|
|
} from '@heroicons/react/20/solid'
|
2023-01-24 16:54:24 -08:00
|
|
|
import FormatNumericValue from '@components/shared/FormatNumericValue'
|
2023-02-23 09:28:09 -08:00
|
|
|
import { CategoricalChartFunc } from 'recharts/types/chart/generateCategoricalChart'
|
2023-04-13 22:22:39 -07:00
|
|
|
import { interpolateNumber } from 'd3-interpolate'
|
|
|
|
import { IconButton } from '@components/shared/Button'
|
|
|
|
import Tooltip from '@components/shared/Tooltip'
|
2023-08-03 03:34:57 -07:00
|
|
|
import { SwapChartSettings, SwapHistoryItem } from 'types'
|
2023-07-21 11:50:06 -07:00
|
|
|
import useThemeWrapper from 'hooks/useThemeWrapper'
|
2022-07-10 19:01:16 -07:00
|
|
|
|
|
|
|
dayjs.extend(relativeTime)
|
|
|
|
|
2023-08-03 03:34:57 -07:00
|
|
|
export const handleFlipPrices = (
|
|
|
|
flip: boolean,
|
|
|
|
flipPrices: boolean,
|
|
|
|
inputToken: string | undefined,
|
|
|
|
outputToken: string | undefined,
|
|
|
|
swapChartSettings: SwapChartSettings[],
|
|
|
|
setSwapChartSettings: (settings: SwapChartSettings[]) => void,
|
|
|
|
) => {
|
|
|
|
if (!inputToken || !outputToken) return
|
|
|
|
if (!flipPrices && flip) {
|
|
|
|
setSwapChartSettings([
|
|
|
|
...swapChartSettings,
|
|
|
|
{ pair: `${inputToken}/${outputToken}`, flipPrices: true },
|
|
|
|
])
|
|
|
|
} else {
|
|
|
|
setSwapChartSettings(
|
|
|
|
swapChartSettings.filter(
|
|
|
|
(chart: SwapChartSettings) =>
|
|
|
|
!chart.pair.includes(inputToken) && !chart.pair.includes(outputToken),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-14 12:38:43 -07:00
|
|
|
const CustomizedLabel = ({
|
|
|
|
chartData,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
value,
|
2023-01-05 20:38:13 -08:00
|
|
|
index,
|
2022-09-14 12:38:43 -07:00
|
|
|
}: {
|
2023-02-23 09:28:09 -08:00
|
|
|
chartData: ChartDataItem[]
|
2022-09-14 12:38:43 -07:00
|
|
|
x?: number
|
|
|
|
y?: string | number
|
|
|
|
value?: number
|
2023-01-05 20:38:13 -08:00
|
|
|
index?: number
|
2022-09-14 12:38:43 -07:00
|
|
|
}) => {
|
|
|
|
const { width } = useViewport()
|
|
|
|
const [min, max] = useMemo(() => {
|
|
|
|
if (chartData.length) {
|
2023-02-23 09:28:09 -08:00
|
|
|
const prices = chartData.map((d) => d.price)
|
2022-09-14 12:38:43 -07:00
|
|
|
return [Math.min(...prices), Math.max(...prices)]
|
|
|
|
}
|
|
|
|
return ['', '']
|
|
|
|
}, [chartData])
|
|
|
|
|
2023-01-05 20:38:13 -08:00
|
|
|
const [minIndex, maxIndex] = useMemo(() => {
|
|
|
|
const minIndex = chartData.findIndex((d) => d.price === min)
|
|
|
|
const maxIndex = chartData.findIndex((d) => d.price === max)
|
|
|
|
return [minIndex, maxIndex]
|
|
|
|
}, [min, max, chartData])
|
|
|
|
|
|
|
|
if (value && (minIndex === index || maxIndex === index)) {
|
2022-09-14 12:38:43 -07:00
|
|
|
return (
|
|
|
|
<Text
|
|
|
|
x={x}
|
|
|
|
y={y}
|
|
|
|
dy={value === min ? 16 : -8}
|
2022-12-04 01:21:32 -08:00
|
|
|
fill="var(--fgd-4)"
|
2022-09-14 12:38:43 -07:00
|
|
|
fontSize={10}
|
|
|
|
textAnchor={x && y && x > width / 3 ? 'end' : 'start'}
|
2022-09-22 22:09:38 -07:00
|
|
|
className="font-mono"
|
2022-09-14 12:38:43 -07:00
|
|
|
>
|
2023-01-24 16:54:24 -08:00
|
|
|
{formatNumericValue(value)}
|
2022-09-14 12:38:43 -07:00
|
|
|
</Text>
|
|
|
|
)
|
|
|
|
} else return <div />
|
|
|
|
}
|
|
|
|
|
2023-04-15 06:56:50 -07:00
|
|
|
interface ExtendedReferenceDotProps extends ReferenceDotProps {
|
|
|
|
swapHistory: SwapHistoryItem[]
|
2023-04-16 17:38:09 -07:00
|
|
|
swapMarketName: string
|
2023-04-15 06:56:50 -07:00
|
|
|
flipPrices: boolean
|
2023-07-19 18:26:13 -07:00
|
|
|
mouseEnter: (
|
|
|
|
swap: SwapHistoryItem | undefined,
|
2023-07-21 11:47:53 -07:00
|
|
|
coingeckoPrice: string | number | undefined,
|
2023-07-19 18:26:13 -07:00
|
|
|
) => void
|
|
|
|
mouseLeave: MouseEventHandler
|
2023-04-15 06:56:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const SwapHistoryArrows = (props: ExtendedReferenceDotProps) => {
|
2023-07-19 18:26:13 -07:00
|
|
|
const {
|
|
|
|
cx,
|
|
|
|
cy,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
swapHistory,
|
|
|
|
swapMarketName,
|
|
|
|
flipPrices,
|
|
|
|
mouseEnter,
|
|
|
|
mouseLeave,
|
|
|
|
} = props
|
2023-04-15 06:56:50 -07:00
|
|
|
const swapDetails = swapHistory.find(
|
2023-07-21 11:47:53 -07:00
|
|
|
(swap) => dayjs(swap.block_datetime).unix() * 1000 === x,
|
2023-04-15 06:56:50 -07:00
|
|
|
)
|
2023-04-16 17:38:09 -07:00
|
|
|
const side =
|
|
|
|
swapDetails?.swap_in_symbol === swapMarketName.split('/')[0]
|
|
|
|
? !flipPrices
|
|
|
|
? 'sell'
|
|
|
|
: 'buy'
|
|
|
|
: !flipPrices
|
|
|
|
? 'buy'
|
|
|
|
: 'sell'
|
2023-04-15 06:56:50 -07:00
|
|
|
|
|
|
|
const buy = {
|
|
|
|
pathCoords: 'M11 0.858312L0.857867 15.0004H21.1421L11 0.858312Z',
|
|
|
|
fill: 'var(--up)',
|
2023-07-19 18:26:13 -07:00
|
|
|
yOffset: 0,
|
2023-04-15 06:56:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const sell = {
|
|
|
|
pathCoords:
|
|
|
|
'M11 14.1427L21.1421 0.000533306L0.857865 0.000529886L11 14.1427Z',
|
|
|
|
fill: 'var(--down)',
|
2023-07-19 18:26:13 -07:00
|
|
|
yOffset: -15,
|
2023-04-15 06:56:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const sideArrowProps =
|
|
|
|
side === 'buy' ? (!flipPrices ? buy : sell) : !flipPrices ? sell : buy
|
2023-07-19 18:26:13 -07:00
|
|
|
|
|
|
|
const coingeckoPrice = y ? Number(y) : 0
|
2023-04-15 06:56:50 -07:00
|
|
|
return cx && cy ? (
|
|
|
|
<svg
|
2023-07-19 18:26:13 -07:00
|
|
|
className="cursor-pointer"
|
|
|
|
width="20"
|
|
|
|
height="15"
|
2023-04-15 06:56:50 -07:00
|
|
|
viewBox="0 0 20 15"
|
|
|
|
fill="none"
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
2023-07-19 18:26:13 -07:00
|
|
|
x={cx - 11}
|
2023-04-15 06:56:50 -07:00
|
|
|
y={cy + sideArrowProps.yOffset}
|
2023-07-19 18:26:13 -07:00
|
|
|
onMouseEnter={() => mouseEnter(swapDetails, coingeckoPrice)}
|
|
|
|
onMouseLeave={mouseLeave}
|
2023-04-15 06:56:50 -07:00
|
|
|
>
|
|
|
|
<path
|
|
|
|
d={sideArrowProps.pathCoords}
|
|
|
|
fill={sideArrowProps.fill}
|
|
|
|
stroke={'var(--bkg-1)'}
|
|
|
|
strokeWidth={2}
|
|
|
|
/>
|
|
|
|
</svg>
|
|
|
|
) : (
|
|
|
|
<div />
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-11-18 11:11:06 -08:00
|
|
|
const SwapTokenChart = () => {
|
2022-12-11 15:21:40 -08:00
|
|
|
const { t } = useTranslation('common')
|
2023-01-12 12:46:30 -08:00
|
|
|
const inputBank = mangoStore((s) => s.swap.inputBank)
|
|
|
|
const outputBank = mangoStore((s) => s.swap.outputBank)
|
2022-11-18 11:11:06 -08:00
|
|
|
const { inputCoingeckoId, outputCoingeckoId } = useJupiterSwapData()
|
|
|
|
const [baseTokenId, setBaseTokenId] = useState(inputCoingeckoId)
|
|
|
|
const [quoteTokenId, setQuoteTokenId] = useState(outputCoingeckoId)
|
2023-02-23 09:28:09 -08:00
|
|
|
const [mouseData, setMouseData] = useState<ChartDataItem>()
|
2022-11-29 21:30:18 -08:00
|
|
|
const [daysToShow, setDaysToShow] = useState('1')
|
2023-08-03 03:34:57 -07:00
|
|
|
// const [flipPrices, setFlipPrices] = useState(false)
|
2023-07-21 11:50:06 -07:00
|
|
|
const { theme } = useThemeWrapper()
|
2022-11-23 18:30:20 -08:00
|
|
|
const [animationSettings] = useLocalStorageState(
|
|
|
|
ANIMATION_SETTINGS_KEY,
|
2023-07-21 11:47:53 -07:00
|
|
|
INITIAL_ANIMATION_SETTINGS,
|
2022-11-23 18:30:20 -08:00
|
|
|
)
|
2023-08-03 03:34:57 -07:00
|
|
|
const [swapChartSettings, setSwapChartSettings] = useLocalStorageState(
|
|
|
|
SWAP_CHART_SETTINGS_KEY,
|
|
|
|
[],
|
|
|
|
)
|
2023-04-13 22:22:39 -07:00
|
|
|
const swapHistory = mangoStore((s) => s.mangoAccount.swapHistory.data)
|
|
|
|
const loadSwapHistory = mangoStore((s) => s.mangoAccount.swapHistory.loading)
|
2023-04-23 18:29:36 -07:00
|
|
|
const [showSwaps, setShowSwaps] = useState(true)
|
2023-07-19 18:26:13 -07:00
|
|
|
const [swapTooltipData, setSwapTooltipData] =
|
|
|
|
useState<SwapHistoryItem | null>(null)
|
|
|
|
const [swapTooltipCoingeckoPrice, setSwapTooltipCoingeckoPrice] = useState<
|
|
|
|
string | number | undefined
|
|
|
|
>(undefined)
|
|
|
|
|
2023-08-03 03:34:57 -07:00
|
|
|
const flipPrices = useMemo(() => {
|
|
|
|
if (!swapChartSettings.length || !inputBank || !outputBank) return false
|
|
|
|
const pairSettings = swapChartSettings.find(
|
|
|
|
(chart: SwapChartSettings) =>
|
|
|
|
chart.pair.includes(inputBank.name) &&
|
|
|
|
chart.pair.includes(outputBank.name),
|
|
|
|
)
|
|
|
|
if (pairSettings) {
|
|
|
|
return pairSettings.flipPrices
|
|
|
|
} else return false
|
|
|
|
}, [swapChartSettings, inputBank, outputBank])
|
|
|
|
|
|
|
|
const swapMarketName = useMemo(() => {
|
|
|
|
if (!inputBank || !outputBank) return ''
|
|
|
|
const inputSymbol = formatTokenSymbol(inputBank.name)
|
|
|
|
const outputSymbol = formatTokenSymbol(outputBank.name)
|
|
|
|
return !flipPrices
|
|
|
|
? `${outputSymbol}/${inputSymbol}`
|
|
|
|
: `${inputSymbol}/${outputSymbol}`
|
|
|
|
}, [flipPrices, inputBank, inputCoingeckoId, outputBank])
|
|
|
|
|
2023-07-20 17:45:03 -07:00
|
|
|
const handleSwapMouseEnter = useCallback(
|
|
|
|
(
|
|
|
|
swap: SwapHistoryItem | undefined,
|
2023-07-21 11:47:53 -07:00
|
|
|
coingeckoPrice: string | number | undefined,
|
2023-07-20 17:45:03 -07:00
|
|
|
) => {
|
|
|
|
if (swap) {
|
|
|
|
setSwapTooltipData(swap)
|
|
|
|
}
|
|
|
|
if (coingeckoPrice) {
|
|
|
|
setSwapTooltipCoingeckoPrice(coingeckoPrice)
|
|
|
|
}
|
|
|
|
},
|
2023-07-21 11:47:53 -07:00
|
|
|
[setSwapTooltipData, setSwapTooltipCoingeckoPrice],
|
2023-07-20 17:45:03 -07:00
|
|
|
)
|
2023-07-19 18:26:13 -07:00
|
|
|
|
2023-07-20 17:45:03 -07:00
|
|
|
const handleSwapMouseLeave = useCallback(() => {
|
2023-07-19 18:26:13 -07:00
|
|
|
setSwapTooltipData(null)
|
2023-07-20 17:45:03 -07:00
|
|
|
}, [setSwapTooltipData])
|
2023-07-19 18:26:13 -07:00
|
|
|
|
2023-07-20 17:45:03 -07:00
|
|
|
const renderTooltipContent = useCallback(
|
|
|
|
(swap: SwapHistoryItem) => {
|
|
|
|
const {
|
|
|
|
swap_in_amount,
|
|
|
|
swap_in_symbol,
|
|
|
|
swap_out_price_usd,
|
|
|
|
swap_out_amount,
|
|
|
|
swap_out_symbol,
|
|
|
|
} = swap
|
|
|
|
|
|
|
|
const swapOutValue = swap_out_price_usd * swap_out_amount
|
|
|
|
|
|
|
|
const baseMarketToken = swapMarketName.split('/')[0]
|
|
|
|
|
|
|
|
const swapSide =
|
|
|
|
swap_in_symbol === baseMarketToken
|
|
|
|
? !flipPrices
|
|
|
|
? 'sell'
|
|
|
|
: 'buy'
|
|
|
|
: !flipPrices
|
|
|
|
? 'buy'
|
|
|
|
: 'sell'
|
|
|
|
|
|
|
|
const buy = {
|
|
|
|
price: swap_in_amount / swap_out_amount,
|
|
|
|
priceSymbol: swap_in_symbol,
|
|
|
|
amount: swap_out_amount,
|
|
|
|
side: 'buy',
|
|
|
|
symbol: swap_out_symbol,
|
|
|
|
value: swapOutValue,
|
|
|
|
}
|
2023-07-19 18:26:13 -07:00
|
|
|
|
2023-07-20 17:45:03 -07:00
|
|
|
const sell = {
|
|
|
|
price: swap_out_amount / swap_in_amount,
|
|
|
|
priceSymbol: swap_out_symbol,
|
|
|
|
amount: swap_in_amount,
|
|
|
|
side: 'sell',
|
|
|
|
symbol: swap_in_symbol,
|
|
|
|
value: swapOutValue,
|
|
|
|
}
|
2023-07-19 18:26:13 -07:00
|
|
|
|
2023-07-20 17:45:03 -07:00
|
|
|
const swapProps =
|
|
|
|
swapSide === 'buy'
|
|
|
|
? !flipPrices
|
|
|
|
? buy
|
|
|
|
: sell
|
|
|
|
: !flipPrices
|
|
|
|
? sell
|
|
|
|
: buy
|
|
|
|
|
|
|
|
const { amount, price, priceSymbol, side, symbol, value } = swapProps
|
|
|
|
|
|
|
|
let coingeckoPercentageDifference = 0
|
|
|
|
if (
|
|
|
|
swapTooltipCoingeckoPrice &&
|
|
|
|
typeof swapTooltipCoingeckoPrice === 'number'
|
|
|
|
) {
|
|
|
|
const difference = ((price - swapTooltipCoingeckoPrice) / price) * 100
|
|
|
|
coingeckoPercentageDifference = difference
|
|
|
|
}
|
2022-11-18 11:11:06 -08:00
|
|
|
|
2023-07-20 17:45:03 -07:00
|
|
|
const betterThanCoingecko =
|
|
|
|
swapSide === 'buy'
|
|
|
|
? flipPrices
|
|
|
|
? coingeckoPercentageDifference > 0
|
|
|
|
: coingeckoPercentageDifference < 0
|
|
|
|
: flipPrices
|
|
|
|
? coingeckoPercentageDifference < 0
|
|
|
|
: coingeckoPercentageDifference > 0
|
2023-07-19 18:26:13 -07:00
|
|
|
|
2023-07-20 17:45:03 -07:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<p className="text-center text-th-fgd-2">{`${t(
|
2023-07-21 11:47:53 -07:00
|
|
|
side,
|
2023-07-20 17:45:03 -07:00
|
|
|
)} ${amount} ${symbol} at ${formatNumericValue(
|
2023-07-21 11:47:53 -07:00
|
|
|
price,
|
2023-07-20 17:45:03 -07:00
|
|
|
)} ${priceSymbol} for ${formatCurrencyValue(value)}`}</p>
|
|
|
|
{coingeckoPercentageDifference ? (
|
|
|
|
<p
|
|
|
|
className={`mt-0.5 text-center text-xs ${
|
|
|
|
betterThanCoingecko ? 'text-th-up' : 'text-th-down'
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<span className="font-mono">
|
|
|
|
{coingeckoPercentageDifference.toFixed(2)}%
|
|
|
|
</span>{' '}
|
|
|
|
{betterThanCoingecko ? 'better than' : 'worse than'} Coingecko
|
|
|
|
</p>
|
|
|
|
) : null}
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
},
|
2023-07-21 11:47:53 -07:00
|
|
|
[flipPrices, swapMarketName, swapTooltipCoingeckoPrice],
|
2023-07-20 17:45:03 -07:00
|
|
|
)
|
2022-11-18 11:11:06 -08:00
|
|
|
|
2023-07-12 15:17:20 -07:00
|
|
|
const {
|
|
|
|
data: coingeckoDataQuery,
|
|
|
|
isLoading,
|
|
|
|
isFetching,
|
|
|
|
} = useQuery(
|
|
|
|
['swap-chart-data', baseTokenId, quoteTokenId, daysToShow],
|
2022-11-18 08:29:37 -08:00
|
|
|
() => fetchChartData(baseTokenId, quoteTokenId, daysToShow),
|
2022-11-18 20:59:06 -08:00
|
|
|
{
|
2022-12-15 12:43:20 -08:00
|
|
|
cacheTime: 1000 * 60 * 15,
|
2022-11-18 20:59:06 -08:00
|
|
|
staleTime: 1000 * 60 * 1,
|
|
|
|
enabled: !!baseTokenId && !!quoteTokenId,
|
2023-01-20 08:13:03 -08:00
|
|
|
refetchOnWindowFocus: false,
|
2023-07-21 11:47:53 -07:00
|
|
|
},
|
2022-10-07 16:39:06 -07:00
|
|
|
)
|
2023-01-12 20:26:07 -08:00
|
|
|
|
2023-04-15 06:56:50 -07:00
|
|
|
const coingeckoData = useMemo(() => {
|
2023-07-12 15:17:20 -07:00
|
|
|
if (!coingeckoDataQuery || !coingeckoDataQuery.length) return []
|
2023-01-12 20:26:07 -08:00
|
|
|
if (!flipPrices) {
|
2023-07-12 15:17:20 -07:00
|
|
|
return coingeckoDataQuery
|
2023-01-12 20:26:07 -08:00
|
|
|
} else {
|
2023-07-12 15:17:20 -07:00
|
|
|
return coingeckoDataQuery.map((d: ChartDataItem) => {
|
2023-02-23 09:28:09 -08:00
|
|
|
const price =
|
|
|
|
d.inputTokenPrice / d.outputTokenPrice === d.price
|
|
|
|
? d.outputTokenPrice / d.inputTokenPrice
|
|
|
|
: d.inputTokenPrice / d.outputTokenPrice
|
2023-01-12 20:26:07 -08:00
|
|
|
return { ...d, price: price }
|
|
|
|
})
|
|
|
|
}
|
2023-04-15 06:56:50 -07:00
|
|
|
}, [flipPrices, coingeckoDataQuery])
|
2022-07-10 19:01:16 -07:00
|
|
|
|
2023-04-13 22:22:39 -07:00
|
|
|
const chartSwapTimes = useMemo(() => {
|
2023-04-15 06:56:50 -07:00
|
|
|
if (
|
|
|
|
loadSwapHistory ||
|
|
|
|
!swapHistory ||
|
|
|
|
!swapHistory.length ||
|
|
|
|
!inputBank ||
|
|
|
|
!outputBank
|
|
|
|
)
|
2023-04-13 22:22:39 -07:00
|
|
|
return []
|
2023-04-23 18:51:46 -07:00
|
|
|
const chartSymbols = [
|
2023-07-19 18:26:13 -07:00
|
|
|
inputBank.name === 'ETH (Portal)' ? 'ETH' : inputBank.name,
|
|
|
|
outputBank.name === 'ETH (Portal)' ? 'ETH' : outputBank.name,
|
2023-04-23 18:51:46 -07:00
|
|
|
]
|
2023-04-13 22:22:39 -07:00
|
|
|
return swapHistory
|
|
|
|
.filter(
|
|
|
|
(swap) =>
|
|
|
|
chartSymbols.includes(swap.swap_in_symbol) &&
|
2023-07-21 11:47:53 -07:00
|
|
|
chartSymbols.includes(swap.swap_out_symbol),
|
2023-04-13 22:22:39 -07:00
|
|
|
)
|
|
|
|
.map((val) => dayjs(val.block_datetime).unix() * 1000)
|
|
|
|
}, [swapHistory, loadSwapHistory, inputBank, outputBank])
|
|
|
|
|
2023-04-15 06:56:50 -07:00
|
|
|
const swapHistoryPoints = useMemo(() => {
|
|
|
|
if (!coingeckoData.length || !chartSwapTimes.length) return []
|
2023-04-13 22:22:39 -07:00
|
|
|
return chartSwapTimes.map((x) => {
|
|
|
|
const makeChartDataItem = { inputTokenPrice: 1, outputTokenPrice: 1 }
|
2023-04-15 06:56:50 -07:00
|
|
|
const index = coingeckoData.findIndex((d) => d.time > x) // find index of data point with x value greater than highlight x
|
2023-04-13 22:22:39 -07:00
|
|
|
if (index === 0) {
|
2023-04-15 06:56:50 -07:00
|
|
|
return { time: x, price: coingeckoData[0].price, ...makeChartDataItem } // return first data point y value if highlight x is less than first data point x
|
2023-04-13 22:22:39 -07:00
|
|
|
} else if (index === -1) {
|
|
|
|
return {
|
|
|
|
time: x,
|
2023-04-15 06:56:50 -07:00
|
|
|
price: coingeckoData[coingeckoData.length - 1].price,
|
2023-04-13 22:22:39 -07:00
|
|
|
...makeChartDataItem,
|
|
|
|
} // return last data point y value if highlight x is greater than last data point x
|
|
|
|
} else {
|
2023-04-15 06:56:50 -07:00
|
|
|
const x0 = coingeckoData[index - 1].time
|
|
|
|
const x1 = coingeckoData[index].time
|
|
|
|
const y0 = coingeckoData[index - 1].price
|
|
|
|
const y1 = coingeckoData[index].price
|
2023-04-13 22:22:39 -07:00
|
|
|
const interpolateY = interpolateNumber(y0, y1) // create interpolate function for y values
|
|
|
|
const y = interpolateY((x - x0) / (x1 - x0)) // estimate y value at highlight x using interpolate function
|
|
|
|
return { time: x, price: y, ...makeChartDataItem }
|
|
|
|
}
|
|
|
|
})
|
2023-04-15 06:56:50 -07:00
|
|
|
}, [coingeckoData, chartSwapTimes])
|
2023-04-13 22:22:39 -07:00
|
|
|
|
2023-08-03 19:06:09 -07:00
|
|
|
const latestChartDataItem = useMemo(() => {
|
|
|
|
if (!inputBank || !outputBank) return []
|
|
|
|
const price = !flipPrices
|
|
|
|
? outputBank.uiPrice / inputBank.uiPrice
|
|
|
|
: inputBank.uiPrice / outputBank.uiPrice
|
|
|
|
const item: ChartDataItem[] = [
|
|
|
|
{
|
|
|
|
price,
|
|
|
|
time: Date.now(),
|
|
|
|
inputTokenPrice: inputBank.uiPrice,
|
|
|
|
outputTokenPrice: outputBank.uiPrice,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
return item
|
|
|
|
}, [flipPrices, inputBank, outputBank])
|
|
|
|
|
2023-04-15 06:56:50 -07:00
|
|
|
const chartData = useMemo(() => {
|
2023-07-12 15:17:20 -07:00
|
|
|
if (!coingeckoData || !coingeckoData.length || coingeckoData.length < 2)
|
|
|
|
return []
|
2023-04-15 06:56:50 -07:00
|
|
|
const minTime = coingeckoData[0].time
|
|
|
|
const maxTime = coingeckoData[coingeckoData.length - 1].time
|
2023-04-16 17:17:05 -07:00
|
|
|
if (swapHistoryPoints.length && showSwaps) {
|
2023-04-15 06:56:50 -07:00
|
|
|
const swapPoints = swapHistoryPoints.filter(
|
2023-07-21 11:47:53 -07:00
|
|
|
(point) => point.time >= minTime && point.time <= maxTime,
|
2023-04-13 22:22:39 -07:00
|
|
|
)
|
2023-08-03 19:06:09 -07:00
|
|
|
return coingeckoData
|
|
|
|
.concat(swapPoints)
|
|
|
|
.sort((a, b) => a.time - b.time)
|
|
|
|
.concat(latestChartDataItem)
|
2023-04-15 06:56:50 -07:00
|
|
|
} else return coingeckoData
|
2023-08-03 20:29:04 -07:00
|
|
|
}, [coingeckoData, latestChartDataItem, swapHistoryPoints, showSwaps])
|
2023-04-13 22:22:39 -07:00
|
|
|
|
2023-07-20 17:45:03 -07:00
|
|
|
const handleMouseMove: CategoricalChartFunc = useCallback(
|
|
|
|
(coords) => {
|
|
|
|
if (coords.activePayload) {
|
|
|
|
setMouseData(coords.activePayload[0].payload)
|
|
|
|
}
|
|
|
|
},
|
2023-07-21 11:47:53 -07:00
|
|
|
[setMouseData],
|
2023-07-20 17:45:03 -07:00
|
|
|
)
|
2022-07-10 19:01:16 -07:00
|
|
|
|
2023-07-20 17:45:03 -07:00
|
|
|
const handleMouseLeave = useCallback(() => {
|
2023-02-23 09:28:09 -08:00
|
|
|
setMouseData(undefined)
|
2023-07-20 17:45:03 -07:00
|
|
|
}, [setMouseData])
|
2022-07-10 19:01:16 -07:00
|
|
|
|
2022-11-18 08:29:37 -08:00
|
|
|
useEffect(() => {
|
2022-11-18 11:11:06 -08:00
|
|
|
if (!inputCoingeckoId || !outputCoingeckoId) return
|
2023-08-03 03:34:57 -07:00
|
|
|
setBaseTokenId(inputCoingeckoId)
|
|
|
|
setQuoteTokenId(outputCoingeckoId)
|
2022-11-18 11:11:06 -08:00
|
|
|
}, [inputCoingeckoId, outputCoingeckoId])
|
2022-11-18 08:29:37 -08:00
|
|
|
|
2023-07-20 17:45:03 -07:00
|
|
|
const calculateChartChange = useCallback(() => {
|
2023-07-12 15:17:20 -07:00
|
|
|
if (!chartData?.length) return 0
|
|
|
|
if (mouseData) {
|
|
|
|
const index = chartData.findIndex((d) => d.time === mouseData.time)
|
2023-08-03 20:29:04 -07:00
|
|
|
if (index === -1) return 0
|
2023-07-12 15:17:20 -07:00
|
|
|
return (
|
|
|
|
((chartData[index]['price'] - chartData[0]['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
|
|
|
}
|
2023-07-20 17:45:03 -07:00
|
|
|
}, [chartData, mouseData])
|
2023-01-12 20:26:07 -08:00
|
|
|
|
2022-07-10 19:01:16 -07:00
|
|
|
return (
|
2022-10-05 19:23:31 -07:00
|
|
|
<ContentBox hideBorder hidePadding className="h-full px-6 py-3">
|
2023-07-12 15:17:20 -07:00
|
|
|
{isLoading || isFetching ? (
|
2022-08-17 13:05:38 -07:00
|
|
|
<>
|
|
|
|
<SheenLoader className="w-[148px] rounded-md">
|
|
|
|
<div className="h-[18px] bg-th-bkg-2" />
|
|
|
|
</SheenLoader>
|
|
|
|
<SheenLoader className="mt-2 w-[148px] rounded-md">
|
|
|
|
<div className="h-[48px] bg-th-bkg-2" />
|
|
|
|
</SheenLoader>
|
|
|
|
<SheenLoader className="mt-2 w-[148px] rounded-md">
|
|
|
|
<div className="h-[18px] bg-th-bkg-2" />
|
|
|
|
</SheenLoader>
|
|
|
|
</>
|
2022-11-18 11:11:06 -08:00
|
|
|
) : chartData?.length && baseTokenId && quoteTokenId ? (
|
2023-07-19 18:26:13 -07:00
|
|
|
<div className="relative h-full">
|
|
|
|
{swapTooltipData ? (
|
|
|
|
<div className="absolute left-1/2 bottom-2 z-10 w-full -translate-x-1/2 rounded-md border border-th-bkg-3 bg-th-bkg-1 px-4 py-2">
|
|
|
|
{renderTooltipContent(swapTooltipData)}
|
|
|
|
</div>
|
|
|
|
) : null}
|
2022-07-10 19:01:16 -07:00
|
|
|
<div className="flex items-start justify-between">
|
|
|
|
<div>
|
2022-10-10 10:28:53 -07:00
|
|
|
{inputBank && outputBank ? (
|
2022-08-18 06:59:36 -07:00
|
|
|
<div className="mb-0.5 flex items-center">
|
2023-01-12 20:26:07 -08:00
|
|
|
<p className="text-base text-th-fgd-3">{swapMarketName}</p>
|
2023-04-13 22:22:39 -07:00
|
|
|
<IconButton
|
|
|
|
className="px-2 text-th-fgd-3"
|
2023-08-03 03:34:57 -07:00
|
|
|
onClick={() =>
|
|
|
|
handleFlipPrices(
|
|
|
|
!flipPrices,
|
|
|
|
flipPrices,
|
|
|
|
inputBank.name,
|
|
|
|
outputBank.name,
|
|
|
|
swapChartSettings,
|
|
|
|
setSwapChartSettings,
|
|
|
|
)
|
|
|
|
}
|
2023-04-13 22:22:39 -07:00
|
|
|
hideBg
|
2022-11-18 08:29:37 -08:00
|
|
|
>
|
2023-04-13 22:22:39 -07:00
|
|
|
<ArrowsRightLeftIcon className="h-5 w-5" />
|
|
|
|
</IconButton>
|
2022-08-18 06:59:36 -07:00
|
|
|
</div>
|
2022-07-10 19:01:16 -07:00
|
|
|
) : null}
|
|
|
|
{mouseData ? (
|
|
|
|
<>
|
2022-12-13 15:16:50 -08:00
|
|
|
<div className="mb-1 flex flex-col font-display text-5xl text-th-fgd-1 md:flex-row md:items-end">
|
2022-11-23 18:30:20 -08:00
|
|
|
{animationSettings['number-scroll'] ? (
|
|
|
|
<FlipNumbers
|
|
|
|
height={48}
|
2022-12-13 15:16:50 -08:00
|
|
|
width={35}
|
2022-11-23 18:30:20 -08:00
|
|
|
play
|
2023-02-23 09:28:09 -08:00
|
|
|
numbers={formatNumericValue(mouseData.price)}
|
2022-11-23 18:30:20 -08:00
|
|
|
/>
|
|
|
|
) : (
|
2023-02-23 09:28:09 -08:00
|
|
|
<FormatNumericValue value={mouseData.price} />
|
2022-11-23 18:30:20 -08:00
|
|
|
)}
|
2022-07-10 19:01:16 -07:00
|
|
|
<span
|
2022-09-07 18:52:47 -07:00
|
|
|
className={`ml-0 mt-2 flex items-center text-sm md:ml-3 md:mt-0`}
|
2022-07-10 19:01:16 -07:00
|
|
|
>
|
2022-12-06 03:58:22 -08:00
|
|
|
<Change change={calculateChartChange()} suffix="%" />
|
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">
|
2023-02-23 09:28:09 -08: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-12-13 02:49:56 -08:00
|
|
|
<div className="mb-1 flex flex-col font-display text-5xl text-th-fgd-1 md:flex-row md:items-end">
|
2022-11-23 18:30:20 -08:00
|
|
|
{animationSettings['number-scroll'] ? (
|
|
|
|
<FlipNumbers
|
|
|
|
height={48}
|
2022-12-13 15:16:50 -08:00
|
|
|
width={35}
|
2022-11-23 18:30:20 -08:00
|
|
|
play
|
2023-01-24 16:54:24 -08:00
|
|
|
numbers={formatNumericValue(
|
2023-07-21 11:47:53 -07:00
|
|
|
chartData[chartData.length - 1].price,
|
2022-11-23 18:30:20 -08:00
|
|
|
)}
|
|
|
|
/>
|
|
|
|
) : (
|
2023-01-24 16:54:24 -08:00
|
|
|
<FormatNumericValue
|
2023-02-23 09:28:09 -08:00
|
|
|
value={chartData[chartData.length - 1].price}
|
2023-01-24 16:54:24 -08:00
|
|
|
/>
|
2022-11-23 18:30:20 -08:00
|
|
|
)}
|
2022-07-10 19:01:16 -07:00
|
|
|
<span
|
2022-09-07 18:52:47 -07:00
|
|
|
className={`ml-0 mt-2 flex items-center text-sm md:ml-3 md:mt-0`}
|
2022-07-10 19:01:16 -07:00
|
|
|
>
|
2022-12-06 03:58:22 -08:00
|
|
|
<Change change={calculateChartChange()} suffix="%" />
|
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">
|
2023-02-23 09:28:09 -08:00
|
|
|
{dayjs(chartData[chartData.length - 1].time).format(
|
2023-07-21 11:47:53 -07:00
|
|
|
'DD MMM YY, h:mma',
|
2022-11-18 08:29:37 -08:00
|
|
|
)}
|
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>
|
2023-06-14 21:42:34 -07:00
|
|
|
<div className="mt-2 h-40 w-auto md:h-96">
|
2023-04-13 22:22:39 -07:00
|
|
|
<div className="absolute top-[2px] right-0 -mb-2 flex items-center justify-end space-x-4">
|
|
|
|
<Tooltip
|
|
|
|
content={
|
|
|
|
showSwaps
|
|
|
|
? t('swap:hide-swap-history')
|
|
|
|
: t('swap:show-swap-history')
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<IconButton
|
|
|
|
className="text-th-fgd-3"
|
|
|
|
hideBg
|
|
|
|
onClick={() => setShowSwaps(!showSwaps)}
|
|
|
|
>
|
|
|
|
{showSwaps ? (
|
|
|
|
<EyeIcon className="h-5 w-5" />
|
|
|
|
) : (
|
|
|
|
<EyeSlashIcon className="h-5 w-5" />
|
|
|
|
)}
|
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
2022-09-07 23:25:32 -07:00
|
|
|
<ChartRangeButtons
|
|
|
|
activeValue={daysToShow}
|
|
|
|
names={['24H', '7D', '30D']}
|
2022-11-29 21:30:18 -08:00
|
|
|
values={['1', '7', '30']}
|
2022-09-07 23:25:32 -07:00
|
|
|
onChange={(v) => setDaysToShow(v)}
|
|
|
|
/>
|
2022-07-10 19:01:16 -07:00
|
|
|
</div>
|
2022-09-14 14:57:12 -07:00
|
|
|
<div className="h-full md:-mx-2 md:mt-4">
|
2022-09-14 12:38:43 -07:00
|
|
|
<ResponsiveContainer>
|
2022-07-12 20:58:13 -07:00
|
|
|
<AreaChart
|
|
|
|
data={chartData}
|
|
|
|
onMouseMove={handleMouseMove}
|
|
|
|
onMouseLeave={handleMouseLeave}
|
|
|
|
>
|
2023-04-13 22:22:39 -07:00
|
|
|
<RechartsTooltip
|
2022-07-12 20:58:13 -07:00
|
|
|
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
|
2022-11-30 19:32:32 -08:00
|
|
|
? COLORS.UP[theme]
|
|
|
|
: COLORS.DOWN[theme]
|
2022-08-02 17:17:42 -07:00
|
|
|
}
|
2022-09-14 12:38:43 -07:00
|
|
|
stopOpacity={0.25}
|
2022-07-15 13:26:29 -07:00
|
|
|
/>
|
|
|
|
<stop
|
|
|
|
offset="99%"
|
2022-08-02 17:17:42 -07:00
|
|
|
stopColor={
|
|
|
|
calculateChartChange() >= 0
|
2022-11-30 19:32:32 -08:00
|
|
|
? COLORS.UP[theme]
|
|
|
|
: COLORS.DOWN[theme]
|
2022-08-02 17:17:42 -07:00
|
|
|
}
|
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
|
2022-11-30 19:32:32 -08:00
|
|
|
? COLORS.UP[theme]
|
|
|
|
: COLORS.DOWN[theme]
|
2022-08-02 17:17:42 -07:00
|
|
|
}
|
2022-07-18 20:58:21 -07:00
|
|
|
strokeWidth={1.5}
|
2022-07-12 20:58:13 -07:00
|
|
|
fill="url(#gradientArea)"
|
2022-09-14 12:38:43 -07:00
|
|
|
label={<CustomizedLabel chartData={chartData} />}
|
2022-07-12 20:58:13 -07:00
|
|
|
/>
|
2022-09-14 12:38:43 -07:00
|
|
|
<XAxis dataKey="time" hide padding={{ left: 0, right: 0 }} />
|
2022-07-12 20:58:13 -07:00
|
|
|
<YAxis
|
|
|
|
dataKey="price"
|
|
|
|
type="number"
|
|
|
|
domain={['dataMin', 'dataMax']}
|
|
|
|
hide
|
|
|
|
padding={{ top: 20, bottom: 20 }}
|
|
|
|
/>
|
2023-04-15 06:56:50 -07:00
|
|
|
{showSwaps && swapHistoryPoints.length
|
|
|
|
? swapHistoryPoints.map((point, index) => (
|
2023-04-13 22:22:39 -07:00
|
|
|
<ReferenceDot
|
|
|
|
key={index}
|
|
|
|
x={point.time}
|
|
|
|
y={point.price}
|
|
|
|
isFront={true}
|
2023-04-15 06:56:50 -07:00
|
|
|
shape={
|
|
|
|
<SwapHistoryArrows
|
|
|
|
swapHistory={swapHistory}
|
2023-04-16 17:38:09 -07:00
|
|
|
swapMarketName={swapMarketName}
|
2023-04-15 06:56:50 -07:00
|
|
|
flipPrices={flipPrices}
|
2023-07-19 18:26:13 -07:00
|
|
|
mouseEnter={handleSwapMouseEnter}
|
|
|
|
mouseLeave={handleSwapMouseLeave}
|
2023-04-15 06:56:50 -07:00
|
|
|
/>
|
|
|
|
}
|
2023-04-13 22:22:39 -07:00
|
|
|
/>
|
|
|
|
))
|
|
|
|
: null}
|
2022-07-12 20:58:13 -07:00
|
|
|
</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-12-11 15:21:40 -08:00
|
|
|
<div className="mt-4 flex h-full items-center justify-center p-4 text-th-fgd-3 md:mt-0">
|
2022-07-22 10:07:55 -07:00
|
|
|
<div className="">
|
2022-12-11 15:21:40 -08:00
|
|
|
<NoSymbolIcon className="mx-auto mb-1 h-6 w-6 text-th-fgd-4" />
|
|
|
|
<p className="text-th-fgd-4">{t('chart-unavailable')}</p>
|
2022-07-22 10:07:55 -07:00
|
|
|
</div>
|
2022-07-10 19:01:16 -07:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</ContentBox>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default SwapTokenChart
|