Commit 3699cb41 authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #1722 from blockscout/fe-1721

stats price diff on the homepage
parents 9933b2a2 f56c38e7
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path fill="currentColor" d="M10.56 5.27A.72.72 0 0 0 10 5a.72.72 0 0 0-.56.27l-6.3 8.585a.729.729 0 0 0-.066.75A.699.699 0 0 0 3.7 15h12.6a.699.699 0 0 0 .626-.396.728.728 0 0 0-.066-.749l-6.3-8.585Z"/>
</svg>
...@@ -135,6 +135,7 @@ ...@@ -135,6 +135,7 @@
| "txn_batches" | "txn_batches"
| "unfinalized" | "unfinalized"
| "uniswap" | "uniswap"
| "up"
| "user_op_slim" | "user_op_slim"
| "user_op" | "user_op"
| "validator" | "validator"
......
...@@ -7,18 +7,20 @@ import type { ChainIndicatorId } from 'types/homepage'; ...@@ -7,18 +7,20 @@ import type { ChainIndicatorId } from 'types/homepage';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import IconSvg from 'ui/shared/IconSvg';
interface Props { interface Props {
id: ChainIndicatorId; id: ChainIndicatorId;
title: string; title: string;
value: (stats: HomeStats) => string; value: (stats: HomeStats) => string;
valueDiff?: (stats?: HomeStats) => number | null | undefined;
icon: React.ReactNode; icon: React.ReactNode;
isSelected: boolean; isSelected: boolean;
onClick: (id: ChainIndicatorId) => void; onClick: (id: ChainIndicatorId) => void;
stats: UseQueryResult<HomeStats, ResourceError<unknown>>; stats: UseQueryResult<HomeStats, ResourceError<unknown>>;
} }
const ChainIndicatorItem = ({ id, title, value, icon, isSelected, onClick, stats }: Props) => { const ChainIndicatorItem = ({ id, title, value, valueDiff, icon, isSelected, onClick, stats }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const activeBgColorDesktop = useColorModeValue('white', 'gray.900'); const activeBgColorDesktop = useColorModeValue('white', 'gray.900');
...@@ -53,6 +55,25 @@ const ChainIndicatorItem = ({ id, title, value, icon, isSelected, onClick, stats ...@@ -53,6 +55,25 @@ const ChainIndicatorItem = ({ id, title, value, icon, isSelected, onClick, stats
return <Text variant="secondary" fontWeight={ 600 }>{ value(stats.data) }</Text>; return <Text variant="secondary" fontWeight={ 600 }>{ value(stats.data) }</Text>;
})(); })();
const valueDiffContent = (() => {
if (isMobile || !valueDiff) {
return null;
}
const diff = valueDiff(stats.data);
if (diff === undefined || diff === null) {
return null;
}
const diffColor = diff >= 0 ? 'green.500' : 'red.500';
return (
<Skeleton isLoaded={ !stats.isPlaceholderData } ml={ 3 } display="flex" alignItems="center" color={ diffColor }>
<IconSvg name="up" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
<Text color={ diffColor } fontWeight={ 600 }>{ diff }%</Text>
</Skeleton>
);
})();
return ( return (
<Flex <Flex
alignItems="center" alignItems="center"
...@@ -73,7 +94,10 @@ const ChainIndicatorItem = ({ id, title, value, icon, isSelected, onClick, stats ...@@ -73,7 +94,10 @@ const ChainIndicatorItem = ({ id, title, value, icon, isSelected, onClick, stats
{ icon } { icon }
<Box> <Box>
<Text fontFamily="heading" fontWeight={ 500 }>{ title }</Text> <Text fontFamily="heading" fontWeight={ 500 }>{ title }</Text>
{ valueContent } <Flex alignItems="center">
{ valueContent }
{ valueDiffContent }
</Flex>
</Box> </Box>
</Flex> </Flex>
); );
......
import { Flex, Skeleton, Text, useColorModeValue } from '@chakra-ui/react'; import { Box, Flex, Skeleton, Text, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { HOMEPAGE_STATS } from 'stubs/stats'; import { HOMEPAGE_STATS } from 'stubs/stats';
import Hint from 'ui/shared/Hint'; import Hint from 'ui/shared/Hint';
import IconSvg from 'ui/shared/IconSvg';
import ChainIndicatorChartContainer from './ChainIndicatorChartContainer'; import ChainIndicatorChartContainer from './ChainIndicatorChartContainer';
import ChainIndicatorItem from './ChainIndicatorItem'; import ChainIndicatorItem from './ChainIndicatorItem';
...@@ -56,12 +57,32 @@ const ChainIndicators = () => { ...@@ -56,12 +57,32 @@ const ChainIndicators = () => {
} }
return ( return (
<Text fontWeight={ 600 } fontFamily="heading" fontSize="48px" lineHeight="48px" mt={ 3 } mb={ 4 }> <Text fontWeight={ 600 } fontFamily="heading" fontSize="48px" lineHeight="48px" mt={ 3 }>
{ indicator?.value(statsQueryResult.data) } { indicator?.value(statsQueryResult.data) }
</Text> </Text>
); );
})(); })();
const valueDiff = (() => {
if (!statsQueryResult.data || !indicator?.valueDiff) {
return null;
}
const diff = indicator.valueDiff(statsQueryResult.data);
if (diff === undefined || diff === null) {
return null;
}
const diffColor = diff >= 0 ? 'green.500' : 'red.500';
return (
<Skeleton isLoaded={ !statsQueryResult.isPlaceholderData } display="flex" alignItems="center" color={ diffColor } mt={ 2 }>
<IconSvg name="up" boxSize={ 5 } mr={ 1 } transform={ diff < 0 ? 'rotate(180deg)' : 'rotate(0)' }/>
<Text color={ diffColor } fontWeight={ 600 }>{ diff }%</Text>
</Skeleton>
);
})();
return ( return (
<Flex <Flex
p={{ base: 0, lg: 8 }} p={{ base: 0, lg: 8 }}
...@@ -80,7 +101,10 @@ const ChainIndicators = () => { ...@@ -80,7 +101,10 @@ const ChainIndicators = () => {
<Text fontWeight={ 500 } fontFamily="heading" fontSize="lg">{ indicator?.title }</Text> <Text fontWeight={ 500 } fontFamily="heading" fontSize="lg">{ indicator?.title }</Text>
{ indicator?.hint && <Hint label={ indicator.hint } ml={ 1 }/> } { indicator?.hint && <Hint label={ indicator.hint } ml={ 1 }/> }
</Flex> </Flex>
{ valueTitle } <Box mb={ 4 }>
{ valueTitle }
{ valueDiff }
</Box>
<ChainIndicatorChartContainer { ...queryResult }/> <ChainIndicatorChartContainer { ...queryResult }/>
</Flex> </Flex>
{ indicators.length > 1 && ( { indicators.length > 1 && (
......
...@@ -10,6 +10,7 @@ export interface TChainIndicator<R extends ChartsResources> { ...@@ -10,6 +10,7 @@ export interface TChainIndicator<R extends ChartsResources> {
id: ChainIndicatorId; id: ChainIndicatorId;
title: string; title: string;
value: (stats: HomeStats) => string; value: (stats: HomeStats) => string;
valueDiff?: (stats?: HomeStats) => number | null | undefined;
icon: React.ReactNode; icon: React.ReactNode;
hint?: string; hint?: string;
api: { api: {
......
...@@ -54,6 +54,7 @@ const coinPriceIndicator: TChainIndicator<'stats_charts_market'> = { ...@@ -54,6 +54,7 @@ const coinPriceIndicator: TChainIndicator<'stats_charts_market'> = {
value: (stats) => stats.coin_price === null ? value: (stats) => stats.coin_price === null ?
'$N/A' : '$N/A' :
'$' + Number(stats.coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }), '$' + Number(stats.coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }),
valueDiff: (stats) => stats?.coin_price !== null ? stats?.coin_price_change_percentage : null,
icon: <TokenEntity.Icon token={ nativeTokenData } boxSize={ 6 } marginRight={ 0 }/>, icon: <TokenEntity.Icon token={ nativeTokenData } boxSize={ 6 } marginRight={ 0 }/>,
hint: `${ config.chain.governanceToken.symbol || config.chain.currency.symbol } token daily price in USD.`, hint: `${ config.chain.governanceToken.symbol || config.chain.currency.symbol } token daily price in USD.`,
api: { api: {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment