Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 79 additions & 12 deletions src/features/documentation/landing/desktop/bannerProductSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Button, Col, Row, Tag, Tooltip, Spin } from 'antd';
import { motion } from 'framer-motion';
import { useNavigate } from 'react-router-dom';
import {
useCallback,
useEffect,
useMemo,
useRef,
Expand Down Expand Up @@ -51,7 +52,9 @@ export function BannerProductSection({ productData }: ProductBannerProps) {
};
}, [productData]);

const { data, loading } = useFetchPoolsSummaryByParams(params);
const { data, loading, error } = useFetchPoolsSummaryByParams(params, {
includeDefaultWhereFilters: false,
});

const poolsByKey = useMemo(() => {
const map: Record<string, { tvl: number; volume: number }> = {};
Expand Down Expand Up @@ -80,6 +83,17 @@ export function BannerProductSection({ productData }: ProductBannerProps) {
const timersRef = useRef<Record<string, number>>({});
const [flashByKey, setFlashByKey] = useState<Record<string, FlashDir>>({});

const computeItdPerfPct = useCallback(
(currentPrice?: number, inceptionPrice?: number) => {
const cur = Number(currentPrice);
const inc = Number(inceptionPrice);
if (!Number.isFinite(cur) || !Number.isFinite(inc) || inc === 0)
return null;
return (cur / inc - 1) * 100;
},
[]
);

useEffect(() => {
productData.forEach((p) => {
const k = `${p.poolChain}:${p.poolId.toLowerCase()}`;
Expand Down Expand Up @@ -118,7 +132,7 @@ export function BannerProductSection({ productData }: ProductBannerProps) {
Object.values(timersRef.current).forEach((t) => window.clearTimeout(t));
timersRef.current = {};
};
}, [neededPrices, productData]);
}, [computeItdPerfPct, neededPrices, productData]);

const handleNavigation = (route?: string) => {
if (route) navigate(route);
Expand All @@ -134,16 +148,69 @@ export function BannerProductSection({ productData }: ProductBannerProps) {
const formatPct = (value: number) =>
`${value >= 0 ? '+' : ''}${value.toFixed(1)}%`;

const computeItdPerfPct = (
currentPrice?: number,
inceptionPrice?: number
) => {
const cur = Number(currentPrice);
const inc = Number(inceptionPrice);
if (!Number.isFinite(cur) || !Number.isFinite(inc) || inc === 0)
return null;
return (cur / inc - 1) * 100;
};
const featuredPoolDiagnostics = useMemo(
() =>
productData.map((tag) => {
const summaryKey = `${tag.poolChain}:${tag.poolId}`;
const priceKey = `${tag.poolChain}:${tag.poolId.toLowerCase()}`;
const poolSummary = poolsByKey[summaryKey];
const currentPrice = neededPrices[priceKey];
const itdPerfPct = computeItdPerfPct(currentPrice, tag.inceptionLpPrice);

return {
title: tag.title,
status: tag.status,
poolId: tag.poolId,
poolChain: tag.poolChain,
summaryKey,
priceKey,
summaryFound: Boolean(poolSummary),
tvl: poolSummary?.tvl ?? null,
volume24h: poolSummary?.volume ?? null,
currentPrice: currentPrice ?? null,
inceptionLpPrice: tag.inceptionLpPrice,
itdPerfPct,
};
}),
[computeItdPerfPct, neededPrices, poolsByKey, productData]
);

useEffect(() => {
if (loading) {
return;
}

console.groupCollapsed('[landing-page][featured-btf] pool item diagnostics');
console.log('featured pool config', productData);
console.log('pool summary query params', params);
console.log('pool summary raw response', data?.poolGetPools ?? []);
console.log('pool summary error', error ?? null);
console.log('mapped pool summaries', poolsByKey);
console.log('landing page LP prices', neededPrices);
console.log('featured pool diagnostics', featuredPoolDiagnostics);

const missingPoolSummaries = featuredPoolDiagnostics.filter(
(item) => !item.summaryFound
);

if (missingPoolSummaries.length > 0) {
console.warn(
'featured pools missing summary data',
missingPoolSummaries
);
}

console.groupEnd();
}, [
data,
error,
featuredPoolDiagnostics,
loading,
neededPrices,
params,
poolsByKey,
productData,
]);

return (
<motion.div
Expand Down
45 changes: 26 additions & 19 deletions src/features/documentation/landing/landingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,32 @@ export default function LandingPage() {
const dispatch = useAppDispatch();
const handleGateClose = () => dispatch(setAcceptedTermsAndConditions(true));

const productData = CURRENT_LIVE_FACTSHEETS.factsheets.map((factsheet) => ({
title: factsheet.iconTitle,
imgSrc: factsheet.factsheetImage.image,
description: factsheet.iconDescription,
status: factsheet.status,
opacity: factsheet.iconOpacity,
imgWidth: '30%',
focus: factsheet.iconFocus,
poolId: factsheet.poolId,
poolChain: factsheet.poolChain,
inceptionLpPrice: factsheet.inceptionLpPrice,
factsheetRoute: '/factsheet/' + factsheet.poolId,
productExplorerRoute:
ROUTES.PRODUCT_EXPLORER +
'/' +
factsheet.poolChain.toUpperCase() +
'/' +
factsheet.poolId,
}));
const featuredPoolIds = new Set<string>([
ROUTES.TRUFLATIONBITCOINFACTSHEET,
ROUTES.SAFEHAVENFACTSHEET,
]);

const productData = CURRENT_LIVE_FACTSHEETS.factsheets
.filter((factsheet) => featuredPoolIds.has(factsheet.poolId))
.map((factsheet) => ({
title: factsheet.iconTitle,
imgSrc: factsheet.factsheetImage.image,
description: factsheet.iconDescription,
status: factsheet.status,
opacity: factsheet.iconOpacity,
imgWidth: '30%',
focus: factsheet.iconFocus,
poolId: factsheet.poolId,
poolChain: factsheet.poolChain,
inceptionLpPrice: factsheet.inceptionLpPrice,
factsheetRoute: '/factsheet/' + factsheet.poolId,
productExplorerRoute:
ROUTES.PRODUCT_EXPLORER +
'/' +
factsheet.poolChain.toUpperCase() +
'/' +
factsheet.poolId,
}));

//stub
//productData.push({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.notice {
width: 100%;
box-sizing: border-box;
padding: 10px 14px;
border: 1px solid rgba(255, 166, 0, 0.5);
border-radius: 12px;
background: rgba(255, 166, 0, 0.14);
color: #ffd28b;
font-size: 0.95rem;
line-height: 1.45;
}

@media (max-width: 768px) {
.notice {
padding: 10px 12px;
font-size: 0.9rem;
}
}
20 changes: 20 additions & 0 deletions src/features/productDetail/productDetailAnalyticsNotice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FC, memo } from 'react';

import styles from './productDetailAnalyticsNotice.module.scss';

const NOTICE_MESSAGE =
'Token pricing API is currently down for analytics only. LP token pricing on the balancer page and the QuantAMM landing page is still active';

export const ProductDetailAnalyticsNotice: FC = memo(
function ProductDetailAnalyticsNoticeImpl() {
return (
<div
className={styles.notice}
role="status"
aria-live="polite"
>
{NOTICE_MESSAGE}
</div>
);
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
}
}

.product-detail-graph__notice {
margin-bottom: 12px;
}

.product-detail-graph__top-right {
grid-area: top-right;
@include grid-area;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
AgTimeAxisOptions,
AgTooltipRendererResult,
} from 'ag-charts-community';
import { time } from 'ag-charts-community';
import { Grid, Row, Col } from 'antd';

import styles from './productDetailPoolGraph.module.scss';
Expand All @@ -26,6 +27,7 @@ import {
} from './components/productDetailDropdownSelect';
import { ProductDetailGraphTimeRangeSelector } from './components/productDetailGraphTimeRangeSelector';
import { filterByTimeRange } from './helpers';
import { getProductDetailTimeAxisPreset } from './productDetailPoolGraphUtils';

const { useBreakpoint } = Grid;

Expand Down Expand Up @@ -260,46 +262,6 @@ const ProductDetailPoolGraphImpl: FC<ProductDetailPoolGraphImplProps> = ({
() => [...firstAxisSeries, ...secondaryAxisSeries],
[firstAxisSeries, secondaryAxisSeries]
);
// ---- time helpers (ms) ----
const HOUR = 60 * 60 * 1000;
const DAY = 24 * HOUR;
const WEEK = 7 * DAY;
const MONTH = 30 * DAY;
const getTimeAxisIntervalStepMs = useCallback(
(range: string | undefined, spanMs: number): number => {
const maxDataPoints = 4;
const oneDay = DAY;

if (isMobile) {
const daysForThreeLabels = Math.ceil(spanMs / (2 * oneDay));
return daysForThreeLabels * oneDay; // Adjusted for 3 labels on mobile
}

switch (range) {
case '7d':
return DAY;
case '1m':
return 2 * DAY;
case '3m':
return WEEK;
case '6m':
return 2 * WEEK;
case '1y':
return MONTH;
case 'max':
return Math.max(MONTH, Math.round(spanMs / 12));
default: {
if (spanMs <= maxDataPoints * oneDay) {
return oneDay; // Daily steps
} else {
const interval = Math.ceil(spanMs / (maxDataPoints * oneDay));
return interval * oneDay; // Adjusted interval to fit max data points
}
}
}
},
[DAY, MONTH, WEEK, isMobile]
);

const totalSpanMs = useMemo(() => {
if (filteredTs.length < 2) return 0;
Expand All @@ -308,31 +270,41 @@ const ProductDetailPoolGraphImpl: FC<ProductDetailPoolGraphImplProps> = ({
return last - first;
}, [filteredTs]);

const timeIntervalStepMs = useMemo(
() => getTimeAxisIntervalStepMs(selectedTimeRange, totalSpanMs),
[getTimeAxisIntervalStepMs, selectedTimeRange, totalSpanMs]
const timeAxisPreset = useMemo(
() => getProductDetailTimeAxisPreset(totalSpanMs, isMobile),
[totalSpanMs, isMobile]
);

const timeLabelFormat = useMemo(() => {
if (!totalSpanMs) return '%b %Y';
return totalSpanMs <= 90 * DAY
? isMobile
? '%m/%y'
: '%d %b %Y'
: '%b %Y';
}, [totalSpanMs, DAY, isMobile]);
const axes: (AgNumberAxisOptions | AgTimeAxisOptions)[] = useMemo(() => {
const maxVol = filteredTs.length
? Math.max(...filteredTs.map((d) => d.volume24h ?? 0))
: 0;

const timeAxisStep =
timeAxisPreset.intervalUnit === 'day'
? time.day.every(timeAxisPreset.intervalStep)
: timeAxisPreset.intervalUnit === 'week'
? time.monday.every(timeAxisPreset.intervalStep)
: timeAxisPreset.intervalUnit === 'month'
? time.month.every(timeAxisPreset.intervalStep)
: time.year.every(timeAxisPreset.intervalStep);

const base: (AgNumberAxisOptions | AgTimeAxisOptions)[] = [
{
type: 'time',
position: 'bottom',
nice: true,
label: { format: timeLabelFormat },
interval: { step: timeIntervalStepMs },
label: {
format: timeAxisPreset.labelFormat,
autoRotate: true,
autoRotateAngle: 320,
avoidCollisions: true,
minSpacing: timeAxisPreset.labelMinSpacing,
},
interval: {
step: timeAxisStep,
minSpacing: timeAxisPreset.intervalMinSpacing,
},
},
{ type: 'number', position: 'left', keys: ['benchmarkValue', 'value'] },
{
Expand All @@ -352,7 +324,7 @@ const ProductDetailPoolGraphImpl: FC<ProductDetailPoolGraphImplProps> = ({
});
}
return base;
}, [filteredTs, selectedSecondAxis, timeLabelFormat, timeIntervalStepMs]);
}, [filteredTs, selectedSecondAxis, timeAxisPreset]);

const options = useMemo(
() => ({
Expand Down
Loading
Loading