From 7f1247c180a70c02f0187d4401cf929c1661a83c Mon Sep 17 00:00:00 2001 From: 0xonramp <0xonramp@gmail.com> Date: Tue, 10 Dec 2024 22:12:31 -0500 Subject: [PATCH 1/4] feat: APY chart --- src/app/earn/components/chart-tabs.tsx | 1 + src/app/earn/page.tsx | 10 +- src/app/earn/types.ts | 19 +--- src/components/charts/apy-chart.tsx | 53 +++++++++++ src/components/charts/apy-xy-chart.tsx | 119 ++++++++++++++++++++++++ src/components/charts/use-chart-data.ts | 13 ++- 6 files changed, 195 insertions(+), 20 deletions(-) create mode 100644 src/components/charts/apy-chart.tsx create mode 100644 src/components/charts/apy-xy-chart.tsx diff --git a/src/app/earn/components/chart-tabs.tsx b/src/app/earn/components/chart-tabs.tsx index 53608878a..f9dfc136a 100644 --- a/src/app/earn/components/chart-tabs.tsx +++ b/src/app/earn/components/chart-tabs.tsx @@ -8,6 +8,7 @@ type Tab = { name: ChartTab; label: string } const tabs: Tab[] = [ { name: 'price', label: 'Token Price' }, + { name: 'apy', label: 'APY' }, { name: 'tvl', label: 'TVL' }, ] diff --git a/src/app/earn/page.tsx b/src/app/earn/page.tsx index a03f621c2..9874a9816 100644 --- a/src/app/earn/page.tsx +++ b/src/app/earn/page.tsx @@ -6,6 +6,7 @@ import { ChartTabs } from '@/app/earn/components/chart-tabs' import { FaqSection } from '@/app/earn/components/faq-section' import { useEarnContext } from '@/app/earn/provider' import { ChartTab } from '@/app/earn/types' +import { ApyChart } from '@/components/charts/apy-chart' import { PriceChart } from '@/components/charts/price-chart' import { TvlChart } from '@/components/charts/tvl-chart' @@ -13,7 +14,7 @@ import { EarnWidget } from './components/earn-widget' import { QuickStats } from './components/quick-stats' export default function Page() { - const { indexToken, isFetchingStats, nav, tvl } = useEarnContext() + const { indexToken, isFetchingStats, apy, nav, tvl } = useEarnContext() const [currentTab, setCurrentTab] = useState('price') return ( @@ -31,6 +32,13 @@ export default function Page() { nav={nav} /> )} + {currentTab === 'apy' && ( + + )} {currentTab === 'tvl' && ( +
+
+ {formatPercentage(apy)} +
+ +
+
+ +
+
+ +
+ + ) +} diff --git a/src/components/charts/apy-xy-chart.tsx b/src/components/charts/apy-xy-chart.tsx new file mode 100644 index 000000000..9542ec5e0 --- /dev/null +++ b/src/components/charts/apy-xy-chart.tsx @@ -0,0 +1,119 @@ +import { withParentSize } from '@visx/responsive' +import { + AnimatedAreaSeries, + AnimatedLineSeries, + Axis, + Tooltip, + XYChart, +} from '@visx/xychart' +import dayjs from 'dayjs' +import { useMemo } from 'react' + +import { formatPercentage } from '@/app/products/utils/formatters' +import { ChartTooltip } from '@/components/charts/chart-tooltip' +import { LinearGradientFill } from '@/components/charts/linear-gradient-fill' +import { darkTheme, lightTheme } from '@/components/charts/themes' +import { ChartPeriod } from '@/components/charts/types' +import { IndexData } from '@/lib/utils/api/index-data-provider' + +type ApyChartIndexData = Pick + +type Props = { + data: ApyChartIndexData[] + isDark?: boolean + parentWidth: number + parentHeight: number + selectedPeriod: ChartPeriod +} + +const tooltipTimestampFormatByPeriod: { [k in ChartPeriod]: string } = { + [ChartPeriod.Hour]: 'DD MMM YYYY HH:mm', + [ChartPeriod.Day]: 'DD MMM YYYY HH:mm', + [ChartPeriod.Week]: 'DD MMM YYYY HH:mm', + [ChartPeriod.Month]: 'DD MMM YYYY HH:mm', + [ChartPeriod.Year]: 'DD MMM YYYY', +} + +function TvlXYChart({ + data, + isDark = false, + parentWidth, + parentHeight, + selectedPeriod, +}: Props) { + const { minDomainY, maxDomainY } = useMemo(() => { + const apys = data.map(({ APY }) => APY!) + const minApy = Math.min(...apys) + const maxApy = Math.max(...apys) + const diff = maxApy - minApy + return { + minDomainY: minApy - diff * 0.05, + maxDomainY: maxApy + diff * 0.05, + } + }, [data]) + + const seriesAccessors = { + xAccessor: (d: ApyChartIndexData) => new Date(d.CreatedTimestamp), + yAccessor: (d: ApyChartIndexData) => d.APY, + } + + const tooltipAccessors = { + xAccessor: (d: ApyChartIndexData) => + dayjs(d.CreatedTimestamp).format( + tooltipTimestampFormatByPeriod[selectedPeriod], + ), + yAccessor: (d: ApyChartIndexData) => formatPercentage(d.APY), + } + + if (parentHeight === 0 || parentWidth === 0) return null + + return ( + + formatPercentage(d)} + /> + + + + + ( + + )} + /> + + ) +} + +export default withParentSize(TvlXYChart) diff --git a/src/components/charts/use-chart-data.ts b/src/components/charts/use-chart-data.ts index 8db7085c8..8d6fa1ddd 100644 --- a/src/components/charts/use-chart-data.ts +++ b/src/components/charts/use-chart-data.ts @@ -57,7 +57,11 @@ type PartialIndexData = Partial & { CreatedTimestamp: string } -function formatData(data: PartialIndexData[], metric: IndexDataMetric, digits: number = 2) { +function formatData( + data: PartialIndexData[], + metric: IndexDataMetric, + digits: number = 2, +) { if (metric === 'nav') { return data.map((datum) => ({ ...datum, @@ -72,6 +76,13 @@ function formatData(data: PartialIndexData[], metric: IndexDataMetric, digits: n })) } + if (metric === 'apy') { + return data.map((datum) => ({ + ...datum, + APY: Number(datum.APY?.toFixed(4)), + })) + } + return [] } From db17b663ab1fd8a75cf957d4ed6bcd83f003c33f Mon Sep 17 00:00:00 2001 From: 0xonramp <0xonramp@gmail.com> Date: Tue, 31 Dec 2024 00:50:21 -0500 Subject: [PATCH 2/4] Add custom ranges --- src/components/charts/apy-chart.tsx | 4 ++++ src/components/charts/period-selector.tsx | 9 +++++++-- src/components/charts/use-chart-data.ts | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/charts/apy-chart.tsx b/src/components/charts/apy-chart.tsx index 72132881c..2a0667928 100644 --- a/src/components/charts/apy-chart.tsx +++ b/src/components/charts/apy-chart.tsx @@ -1,6 +1,7 @@ import { formatPercentage } from '@/app/products/utils/formatters' import ApyXyChart from '@/components/charts/apy-xy-chart' import { PeriodSelector } from '@/components/charts/period-selector' +import { ChartPeriod } from '@/components/charts/types' import { useChartData } from '@/components/charts/use-chart-data' import { cn } from '@/lib/utils/tailwind' @@ -10,6 +11,8 @@ type Props = { apy: number | null } +const periods = [ChartPeriod.Week, ChartPeriod.Month, ChartPeriod.Year] + export function ApyChart({ indexTokenAddress, isFetchingStats = false, @@ -34,6 +37,7 @@ export function ApyChart({ {formatPercentage(apy)} diff --git a/src/components/charts/period-selector.tsx b/src/components/charts/period-selector.tsx index d7416b754..354a42359 100644 --- a/src/components/charts/period-selector.tsx +++ b/src/components/charts/period-selector.tsx @@ -4,7 +4,7 @@ import { ChartPeriod } from '@/components/charts/types' import { useAnalytics } from '@/lib/hooks/use-analytics' import { cn } from '@/lib/utils/tailwind' -const periods = [ +const defaultPeriods = [ ChartPeriod.Hour, ChartPeriod.Day, ChartPeriod.Week, @@ -13,11 +13,16 @@ const periods = [ ] type Props = { + periods?: ChartPeriod[] selectedPeriod: ChartPeriod setSelectedPeriod: Dispatch> } -export function PeriodSelector({ selectedPeriod, setSelectedPeriod }: Props) { +export function PeriodSelector({ + periods = defaultPeriods, + selectedPeriod, + setSelectedPeriod, +}: Props) { const { logEvent } = useAnalytics() const handleClick = (period: ChartPeriod) => { diff --git a/src/components/charts/use-chart-data.ts b/src/components/charts/use-chart-data.ts index 4ce1a9257..611538ca5 100644 --- a/src/components/charts/use-chart-data.ts +++ b/src/components/charts/use-chart-data.ts @@ -90,7 +90,9 @@ export function useChartData( indexTokenAddress?: string, metric: IndexDataMetric = 'nav', ) { - const [selectedPeriod, setSelectedPeriod] = useState(ChartPeriod.Day) + const [selectedPeriod, setSelectedPeriod] = useState( + metric === 'apy' ? ChartPeriod.Week : ChartPeriod.Day, + ) const [historicalData, setHistoricalData] = useState([]) useQuery({ enabled: isAddress(indexTokenAddress ?? ''), From 3fcc8784963f4401184f84b3653ed9d804112374 Mon Sep 17 00:00:00 2001 From: 0xonramp <0xonramp@gmail.com> Date: Tue, 31 Dec 2024 00:59:10 -0500 Subject: [PATCH 3/4] Refactor formatter --- src/components/charts/apy-xy-chart.tsx | 13 ++----------- src/components/charts/price-xy-chart.tsx | 13 ++----------- src/components/charts/tvl-xy-chart.tsx | 13 ++----------- src/constants/charts.ts | 9 +++++++++ 4 files changed, 15 insertions(+), 33 deletions(-) create mode 100644 src/constants/charts.ts diff --git a/src/components/charts/apy-xy-chart.tsx b/src/components/charts/apy-xy-chart.tsx index 9542ec5e0..6c8362e64 100644 --- a/src/components/charts/apy-xy-chart.tsx +++ b/src/components/charts/apy-xy-chart.tsx @@ -14,6 +14,7 @@ import { ChartTooltip } from '@/components/charts/chart-tooltip' import { LinearGradientFill } from '@/components/charts/linear-gradient-fill' import { darkTheme, lightTheme } from '@/components/charts/themes' import { ChartPeriod } from '@/components/charts/types' +import { tooltipTimestampFormat } from '@/constants/charts' import { IndexData } from '@/lib/utils/api/index-data-provider' type ApyChartIndexData = Pick @@ -26,14 +27,6 @@ type Props = { selectedPeriod: ChartPeriod } -const tooltipTimestampFormatByPeriod: { [k in ChartPeriod]: string } = { - [ChartPeriod.Hour]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Day]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Week]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Month]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Year]: 'DD MMM YYYY', -} - function TvlXYChart({ data, isDark = false, @@ -59,9 +52,7 @@ function TvlXYChart({ const tooltipAccessors = { xAccessor: (d: ApyChartIndexData) => - dayjs(d.CreatedTimestamp).format( - tooltipTimestampFormatByPeriod[selectedPeriod], - ), + dayjs(d.CreatedTimestamp).format(tooltipTimestampFormat[selectedPeriod]), yAccessor: (d: ApyChartIndexData) => formatPercentage(d.APY), } diff --git a/src/components/charts/price-xy-chart.tsx b/src/components/charts/price-xy-chart.tsx index 5c4c9b8f1..f917af24e 100644 --- a/src/components/charts/price-xy-chart.tsx +++ b/src/components/charts/price-xy-chart.tsx @@ -13,6 +13,7 @@ import { ChartTooltip } from '@/components/charts/chart-tooltip' import { LinearGradientFill } from '@/components/charts/linear-gradient-fill' import { darkTheme, lightTheme } from '@/components/charts/themes' import { ChartPeriod } from '@/components/charts/types' +import { tooltipTimestampFormat } from '@/constants/charts' import { formatDollarAmount } from '@/lib/utils' import { IndexData } from '@/lib/utils/api/index-data-provider' @@ -27,14 +28,6 @@ type Props = { selectedPeriod: ChartPeriod } -const tooltipTimestampFormatByPeriod: { [k in ChartPeriod]: string } = { - [ChartPeriod.Hour]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Day]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Week]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Month]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Year]: 'DD MMM YYYY', -} - function PriceXYChart({ data, digits = 2, @@ -61,9 +54,7 @@ function PriceXYChart({ const tooltipAccessors = { xAccessor: (d: PriceChartIndexData) => - dayjs(d.CreatedTimestamp).format( - tooltipTimestampFormatByPeriod[selectedPeriod], - ), + dayjs(d.CreatedTimestamp).format(tooltipTimestampFormat[selectedPeriod]), yAccessor: (d: PriceChartIndexData) => formatDollarAmount(d.NetAssetValue, undefined, digits), } diff --git a/src/components/charts/tvl-xy-chart.tsx b/src/components/charts/tvl-xy-chart.tsx index 97c68c4a3..faa452bd6 100644 --- a/src/components/charts/tvl-xy-chart.tsx +++ b/src/components/charts/tvl-xy-chart.tsx @@ -14,6 +14,7 @@ import { ChartTooltip } from '@/components/charts/chart-tooltip' import { LinearGradientFill } from '@/components/charts/linear-gradient-fill' import { darkTheme, lightTheme } from '@/components/charts/themes' import { ChartPeriod } from '@/components/charts/types' +import { tooltipTimestampFormat } from '@/constants/charts' import { IndexData } from '@/lib/utils/api/index-data-provider' type TvlChartIndexData = Pick< @@ -29,14 +30,6 @@ type Props = { selectedPeriod: ChartPeriod } -const tooltipTimestampFormatByPeriod: { [k in ChartPeriod]: string } = { - [ChartPeriod.Hour]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Day]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Week]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Month]: 'DD MMM YYYY HH:mm', - [ChartPeriod.Year]: 'DD MMM YYYY', -} - function TvlXYChart({ data, isDark = false, @@ -62,9 +55,7 @@ function TvlXYChart({ const tooltipAccessors = { xAccessor: (d: TvlChartIndexData) => - dayjs(d.CreatedTimestamp).format( - tooltipTimestampFormatByPeriod[selectedPeriod], - ), + dayjs(d.CreatedTimestamp).format(tooltipTimestampFormat[selectedPeriod]), yAccessor: (d: TvlChartIndexData) => formatTvl(d.ProductAssetValue), } diff --git a/src/constants/charts.ts b/src/constants/charts.ts new file mode 100644 index 000000000..4d7e9fa66 --- /dev/null +++ b/src/constants/charts.ts @@ -0,0 +1,9 @@ +import { ChartPeriod } from '@/components/charts/types' + +export const tooltipTimestampFormat: { [k in ChartPeriod]: string } = { + [ChartPeriod.Hour]: 'DD MMM YYYY HH:mm', + [ChartPeriod.Day]: 'DD MMM YYYY HH:mm', + [ChartPeriod.Week]: 'DD MMM YYYY HH:mm', + [ChartPeriod.Month]: 'DD MMM YYYY HH:mm', + [ChartPeriod.Year]: 'DD MMM YYYY', +} From d8f3d7786a1bb97d9a60aa33ecb5728ea23cec71 Mon Sep 17 00:00:00 2001 From: 0xonramp <0xonramp@gmail.com> Date: Fri, 3 Jan 2025 17:40:35 -0500 Subject: [PATCH 4/4] adjust based on qa feedback --- src/app/earn/components/chart-tabs.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/earn/components/chart-tabs.tsx b/src/app/earn/components/chart-tabs.tsx index f9dfc136a..caa5e60bd 100644 --- a/src/app/earn/components/chart-tabs.tsx +++ b/src/app/earn/components/chart-tabs.tsx @@ -7,8 +7,8 @@ import { cn } from '@/lib/utils/tailwind' type Tab = { name: ChartTab; label: string } const tabs: Tab[] = [ - { name: 'price', label: 'Token Price' }, - { name: 'apy', label: 'APY' }, + // { name: 'apy', label: 'APY' }, + { name: 'price', label: 'NAV' }, { name: 'tvl', label: 'TVL' }, ]