diff --git a/lib/modules/marketing/useEcosystemPoolActivity.tsx b/lib/modules/marketing/useEcosystemPoolActivity.tsx index 456ff83b4..aacd1e11d 100644 --- a/lib/modules/marketing/useEcosystemPoolActivity.tsx +++ b/lib/modules/marketing/useEcosystemPoolActivity.tsx @@ -2,15 +2,23 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import * as echarts from 'echarts/core' -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState, FC, memo, useCallback, ReactNode } from 'react' import { format } from 'date-fns' import { GqlChain, GqlPoolEventType, GqlToken } from '@/lib/shared/services/api/generated/graphql' import EChartsReactCore from 'echarts-for-react/lib/core' -import { ColorMode, useTheme as useChakraTheme } from '@chakra-ui/react' -import { useTheme as useNextTheme } from 'next-themes' +import { + Box, + Card, + ChakraProvider, + useTheme, + Text, + Link, + Image, + VStack, + HStack, +} from '@chakra-ui/react' import { abbreviateAddress } from '@/lib/shared/utils/addresses' import { useTokens } from '@/lib/modules/tokens/TokensProvider' - import { getBlockExplorerAddressUrl, getBlockExplorerTxUrl, @@ -21,6 +29,7 @@ import { NumberFormatter } from '@/lib/shared/utils/numbers' import { usePoolEvents } from '../pool/usePoolEvents' import { supportedNetworks } from '../web3/ChainConfig' import { getChainShortName } from '@/lib/config/app.config' +import { ArrowUpRight } from 'react-feather' type ChartInfoTokens = { token?: GqlToken @@ -108,27 +117,88 @@ function getDefaultChainMeta() { } } +const CustomTooltip: FC<{ + params: any + currencyFormatter: (value: string) => string + theme: any +}> = ({ params, currencyFormatter, theme }) => { + const data = Array.isArray(params) ? params[0] : params + const timestamp = data.value[0] + const metaData = data.data[2] as ChartInfoMetaData + const { userAddress, tokens, usdValue, tx, chain, type } = metaData + const txLink = getBlockExplorerTxUrl(tx, chain) + const addressLink = getBlockExplorerAddressUrl(userAddress, chain) + + const typeStr = + type === GqlPoolEventType.Add ? 'Add' : type === GqlPoolEventType.Remove ? 'Remove' : 'Swap' + + return ( + + + + + {`${typeStr} ${currencyFormatter( + usdValue + )}`} + + on {getChainShortName(chain)} + + + + {tokens + .filter(token => token.token && Number(token.amount) !== 0) + .map((token, index) => ( + + {token.token?.symbol} + + {Number(Number(token.amount).toFixed(2)).toLocaleString()} {token.token?.symbol} + + + ))} + + + + + + Tx: {format(new Date(timestamp * 1000), 'MMM d, h:mma').toLowerCase()} + + + + + + + + + + By: {abbreviateAddress(userAddress)} + + + + + + + + + + + ) +} + +const MemoizedCustomTooltip = memo(CustomTooltip) + const getDefaultPoolActivityChartOptions = ( - nextTheme: ColorMode = 'dark', theme: any, // TODO: type this currencyFormatter: NumberFormatter, isMobile = false, - is2xl = false + is2xl = false, + tooltipFormatter: (params: any) => string // chain: GqlChain ): echarts.EChartsCoreOption => { - const toolTipTheme = { - heading: 'font-weight: bold; color: #E5D3BE', - container: `background: ${ - nextTheme === 'dark' - ? theme.semanticTokens.colors.background.level3._dark - : theme.semanticTokens.colors.background.default - };`, - text: - nextTheme === 'dark' - ? theme.semanticTokens.colors.font.primary._dark - : theme.semanticTokens.colors.font.primary.default, - } - return { grid: { left: isMobile ? '15%' : '5.5%', @@ -175,92 +245,13 @@ const getDefaultPoolActivityChartOptions = ( splitNumber: 3, }, tooltip: { - triggerOn: 'mousemove|click', + trigger: 'item', confine: is2xl ? false : true, enterable: true, hideDelay: 300, - position: function (point: number[]) { - return [point[0] + 5, point[1] - 5] - }, - extraCssText: `padding-right:2rem;border: none;${toolTipTheme.container};pointer-events: auto!important`, - formatter: (params: any) => { - const data = Array.isArray(params) ? params[0] : params - const timestamp = data.value[0] - const metaData = data.data[2] as ChartInfoMetaData - const userAddress = metaData.userAddress - const tokens = metaData.tokens.filter(token => { - if (!token.token) return false - if (Number(token.amount) === 0) return false - return true - }) as ChartInfoTokens[] - - const tx = metaData.tx - const txLink = getBlockExplorerTxUrl(tx, metaData.chain) - const addressLink = getBlockExplorerAddressUrl(userAddress, metaData.chain) - const typeStr = - metaData.type === GqlPoolEventType.Add - ? 'Add' - : metaData.type === GqlPoolEventType.Remove - ? 'Remove' - : 'Swap' - - const arrow = `` - - return ` -
-
- ${typeStr} -  ${currencyFormatter(metaData.usdValue)}  - on ${getChainShortName(metaData.chain)} -
-
- ${tokens?.map((token, index) => { - return ` -
- -
- ${Number(Number(token.amount).toFixed(2)).toLocaleString()} - ${token.token?.symbol} -
-
- ` - })} -
- - - Tx: ${format(new Date(timestamp * 1000), 'MMM d, h:mma') - .replace('AM', 'am') - .replace('PM', 'pm')} - - - ${arrow} - -
- - By: ${abbreviateAddress( - userAddress - )} - ${arrow} - -
-
- ` - }, + position: [0, 0], // This will be overridden by our custom positioning + extraCssText: `padding-right: 2rem;border: none;background: transparent;pointer-events: auto!important;box-shadow: none;`, + formatter: tooltipFormatter, }, } } @@ -309,19 +300,50 @@ const tabsList: PoolActivityChartTypeTab[] = [ export function useEcosystemPoolActivityChart() { const eChartsRef = useRef(null) const { isMobile, is2xl } = useBreakpoints() - const { theme: nextTheme } = useNextTheme() const { getToken } = useTokens() const { toCurrency } = useCurrency() const [activeTab, setActiveTab] = useState(tabsList[0]) const [activeNetwork, setActiveNetwork] = useState('all') + const [tooltipContent, setTooltipContent] = useState(null) + const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }) - const theme = useChakraTheme() + const theme = useTheme() const { loading, data: response } = usePoolEvents({ first: 500, chainIn: supportedNetworks, }) + const tooltipFormatter = useCallback( + (params: any) => { + setTooltipContent( + + + + ) + return ' ' + }, + [theme, toCurrency] + ) + + const onEvents = useMemo( + () => ({ + mousemove: (params: any) => { + if (params.componentType === 'series') { + setTooltipPosition({ x: params.event.offsetX, y: params.event.offsetY }) + } + }, + mouseout: () => { + setTooltipContent(null) + }, + }), + [] + ) + + const memoizedChartOptions = useMemo(() => { + return getDefaultPoolActivityChartOptions(theme, toCurrency, isMobile, is2xl, tooltipFormatter) + }, [theme, toCurrency, isMobile, is2xl, tooltipFormatter]) + const chartData = useMemo(() => { if (!response) return getDefaultChainMeta() const { poolEvents: events } = response @@ -429,13 +451,7 @@ export function useEcosystemPoolActivityChart() { return { isLoading: loading, - chartOption: getDefaultPoolActivityChartOptions( - nextTheme as ColorMode, - theme, - toCurrency, - isMobile, - is2xl - ), + chartOption: memoizedChartOptions, eChartsRef, chartData, tabsList, @@ -444,5 +460,8 @@ export function useEcosystemPoolActivityChart() { activeNetwork, setActiveNetwork, headerInfo, + tooltipContent, + tooltipPosition, + onEvents, } } diff --git a/lib/shared/components/marketing/EcosystemActivityChart.tsx b/lib/shared/components/marketing/EcosystemActivityChart.tsx index b1b986f18..305145f7c 100644 --- a/lib/shared/components/marketing/EcosystemActivityChart.tsx +++ b/lib/shared/components/marketing/EcosystemActivityChart.tsx @@ -14,9 +14,8 @@ import { VStack, } from '@chakra-ui/react' import ButtonGroup from '@/lib/shared/components/btns/button-group/ButtonGroup' -import { FC, PropsWithChildren } from 'react' +import { FC, PropsWithChildren, useState, useRef, useCallback, useEffect } from 'react' import { motion } from 'framer-motion' - import { EcosystemChainSelect } from './EcosystemChainSelect' import { getChainShortName } from '@/lib/config/app.config' import { supportedNetworks } from '@/lib/modules/web3/ChainConfig' @@ -25,6 +24,8 @@ import { gradientMap, useEcosystemPoolActivityChart, } from '@/lib/modules/marketing/useEcosystemPoolActivity' +import { createPortal } from 'react-dom' +import useMeasure from 'react-use-measure' const AnimateOpacity: FC> = ({ children }) => ( @@ -45,8 +46,15 @@ export function EcosystemActivityChart() { tabsList, headerInfo, eChartsRef, + tooltipContent, + tooltipPosition, + onEvents, } = useEcosystemPoolActivityChart() + const [ref, bounds] = useMeasure({ + scroll: true, + }) + const legendTabs = supportedNetworks.map(key => { return { label: getChainShortName(key), @@ -54,11 +62,43 @@ export function EcosystemActivityChart() { } }) + const [isTooltipVisible, setIsTooltipVisible] = useState(false) + const tooltipRef = useRef(null) + const timeoutRef = useRef(null) + + const showTooltip = useCallback( + (params: any) => { + onEvents.mousemove(params) + setIsTooltipVisible(true) + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + }, + [onEvents] + ) + + const hideTooltip = useCallback(() => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + timeoutRef.current = setTimeout(() => { + onEvents.mouseout() + setIsTooltipVisible(false) + }, 100) + }, [onEvents]) + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + } + }, []) + return ( {isLoading && } - - - { @@ -98,16 +136,41 @@ export function EcosystemActivityChart() { - + + + {/* No idea how to get this to work with */} + {createPortal( +
+ {tooltipContent} +
, + document.body + )} +
- - {legendTabs.map((tab, index) => (