Commit 8fb28abe authored by tom's avatar tom

gas tracker page

parent c647fc2f
...@@ -4,12 +4,12 @@ import React from 'react'; ...@@ -4,12 +4,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
// const GasTracker = dynamic(() => import('ui/pages/GasTracker'), { ssr: false }); const GasTracker = dynamic(() => import('ui/pages/GasTracker'), { ssr: false });
const Page: NextPage = () => { const Page: NextPage = () => {
return ( return (
<PageNextJs pathname="/gas-tracker"> <PageNextJs pathname="/gas-tracker">
{ /* <GasTracker/> */ } <GasTracker/>
</PageNextJs> </PageNextJs>
); );
}; };
......
...@@ -34,4 +34,5 @@ export const ProgressCircleValueText = React.forwardRef< ...@@ -34,4 +34,5 @@ export const ProgressCircleValueText = React.forwardRef<
); );
}); });
export interface ProgressCircleRootProps extends ChakraProgressCircle.RootProps {}
export const ProgressCircleRoot = ChakraProgressCircle.Root; export const ProgressCircleRoot = ChakraProgressCircle.Root;
import { Box, Flex, chakra, useBoolean } from '@chakra-ui/react'; import { Box, Flex, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { STATS_CHARTS } from 'stubs/stats'; import { STATS_CHARTS } from 'stubs/stats';
import { Link } from 'toolkit/chakra/link';
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import LinkInternal from 'ui/shared/links/LinkInternal';
import ChartWidgetContainer from 'ui/stats/ChartWidgetContainer'; import ChartWidgetContainer from 'ui/stats/ChartWidgetContainer';
const GAS_PRICE_CHART_ID = 'averageGasPrice'; const GAS_PRICE_CHART_ID = 'averageGasPrice';
const GasTrackerChart = () => { const GasTrackerChart = () => {
const [ isChartLoadingError, setChartLoadingError ] = useBoolean(false); const [ isChartLoadingError, setChartLoadingError ] = React.useState(false);
const { data, isPlaceholderData, isError } = useApiQuery('stats_lines', { const { data, isPlaceholderData, isError } = useApiQuery('stats_lines', {
queryOptions: { queryOptions: {
placeholderData: STATS_CHARTS, placeholderData: STATS_CHARTS,
}, },
}); });
const handleLoadingError = React.useCallback(() => {
setChartLoadingError(true);
}, []);
const content = (() => { const content = (() => {
if (isPlaceholderData) { if (isPlaceholderData) {
return <ContentLoader/>; return <ContentLoader/>;
...@@ -43,7 +47,7 @@ const GasTrackerChart = () => { ...@@ -43,7 +47,7 @@ const GasTrackerChart = () => {
interval="oneMonth" interval="oneMonth"
units={ chart.units || undefined } units={ chart.units || undefined }
isPlaceholderData={ isPlaceholderData } isPlaceholderData={ isPlaceholderData }
onLoadingError={ setChartLoadingError.on } onLoadingError={ handleLoadingError }
h="320px" h="320px"
/> />
); );
...@@ -53,7 +57,7 @@ const GasTrackerChart = () => { ...@@ -53,7 +57,7 @@ const GasTrackerChart = () => {
<Box> <Box>
<Flex justifyContent="space-between" alignItems="center" mb={ 6 }> <Flex justifyContent="space-between" alignItems="center" mb={ 6 }>
<chakra.h3 textStyle="h3">Gas price history</chakra.h3> <chakra.h3 textStyle="h3">Gas price history</chakra.h3>
<LinkInternal href={ route({ pathname: '/stats', hash: 'gas' }) }>Charts & stats</LinkInternal> <Link href={ route({ pathname: '/stats', hash: 'gas' }) }>Charts & stats</Link>
</Flex> </Flex>
{ content } { content }
</Box> </Box>
......
import { import { Box } from '@chakra-ui/react';
Box,
Heading,
Accordion,
} from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
import { AccordionRoot } from 'toolkit/chakra/accordion';
import { Heading } from 'toolkit/chakra/heading';
import GasTrackerFaqItem from './GasTrackerFaqItem'; import GasTrackerFaqItem from './GasTrackerFaqItem';
...@@ -34,12 +32,12 @@ const FAQ_ITEMS = [ ...@@ -34,12 +32,12 @@ const FAQ_ITEMS = [
const GasTrackerFaq = () => { const GasTrackerFaq = () => {
return ( return (
<Box mt={ 12 }> <Box mt={ 12 }>
<Heading as="h2" mb={ 4 } fontSize="2xl" fontWeight="medium">FAQ</Heading> <Heading level="2" mb={ 4 }>FAQ</Heading>
<Accordion> <AccordionRoot variant="faq">
{ FAQ_ITEMS.map((item, index) => ( { FAQ_ITEMS.map((item, index) => (
<GasTrackerFaqItem key={ index } question={ item.question } answer={ item.answer }/> <GasTrackerFaqItem key={ index } question={ item.question } answer={ item.answer }/>
)) } )) }
</Accordion> </AccordionRoot>
</Box> </Box>
); );
}; };
......
import { import { Text } from '@chakra-ui/react';
AccordionItem,
AccordionButton, import { AccordionItem, AccordionItemTrigger, AccordionItemContent } from 'toolkit/chakra/accordion';
AccordionPanel,
AccordionIcon,
Text,
chakra,
useColorModeValue,
} from '@chakra-ui/react';
interface Props { interface Props {
question: string; question: string;
...@@ -14,24 +8,14 @@ interface Props { ...@@ -14,24 +8,14 @@ interface Props {
} }
const GasTrackerFaqItem = ({ question, answer }: Props) => { const GasTrackerFaqItem = ({ question, answer }: Props) => {
const hoverColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const borderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200');
return ( return (
<AccordionItem as="section" _first={{ borderTopWidth: 0 }} _last={{ borderBottomWidth: 0 }} borderColor={ borderColor }> <AccordionItem as="section" value={ question }>
{ ({ isExpanded }) => ( <AccordionItemTrigger variant="faq" >
<> { question }
<AccordionButton </AccordionItemTrigger>
_hover={{ bgColor: hoverColor }} <AccordionItemContent pb={ 4 } px={ 0 }>
px={ 0 }
>
<chakra.h3 flex="1" textAlign="left" fontSize="lg" fontWeight={ 500 }>{ question }</chakra.h3>
<AccordionIcon transform={ isExpanded ? 'rotate(0deg)' : 'rotate(-90deg)' } color="gray.500"/>
</AccordionButton>
<AccordionPanel pb={ 4 } px={ 0 }>
<Text color="text_secondary">{ answer }</Text> <Text color="text_secondary">{ answer }</Text>
</AccordionPanel> </AccordionItemContent>
</>
) }
</AccordionItem> </AccordionItem>
); );
}; };
......
...@@ -2,7 +2,7 @@ import { chakra } from '@chakra-ui/react'; ...@@ -2,7 +2,7 @@ import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { mdash } from 'lib/html-entities'; import { mdash } from 'lib/html-entities';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
interface Props { interface Props {
percentage: number; percentage: number;
...@@ -30,7 +30,7 @@ const GasTrackerNetworkUtilization = ({ percentage, isLoading }: Props) => { ...@@ -30,7 +30,7 @@ const GasTrackerNetworkUtilization = ({ percentage, isLoading }: Props) => {
const color = colors[load]; const color = colors[load];
return ( return (
<Skeleton isLoaded={ !isLoading } whiteSpace="pre-wrap"> <Skeleton loading={ isLoading } whiteSpace="pre-wrap">
<span>Network utilization </span> <span>Network utilization </span>
<chakra.span color={ color }>{ percentage.toFixed(2) }% { mdash } { load } load</chakra.span> <chakra.span color={ color }>{ percentage.toFixed(2) }% { mdash } { load } load</chakra.span>
</Skeleton> </Skeleton>
......
import { Box, Flex, useColorModeValue } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { GasPriceInfo, GasPrices } from 'types/api/stats'; import type { GasPriceInfo, GasPrices } from 'types/api/stats';
import { SECOND } from 'lib/consts'; import { SECOND } from 'lib/consts';
import { asymp } from 'lib/html-entities'; import { asymp } from 'lib/html-entities';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
import GasPrice from 'ui/shared/gas/GasPrice'; import GasPrice from 'ui/shared/gas/GasPrice';
import type { IconName } from 'ui/shared/IconSvg'; import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -30,10 +30,10 @@ const ICONS: Record<keyof GasPrices, IconName> = { ...@@ -30,10 +30,10 @@ const ICONS: Record<keyof GasPrices, IconName> = {
const GasTrackerPriceSnippet = ({ data, type, isLoading }: Props) => { const GasTrackerPriceSnippet = ({ data, type, isLoading }: Props) => {
const bgColors = { const bgColors = {
fast: 'transparent', fast: 'transparent',
average: useColorModeValue('gray.50', 'whiteAlpha.200'), average: { _light: 'gray.50', _dark: 'whiteAlpha.200' },
slow: useColorModeValue('gray.50', 'whiteAlpha.200'), slow: { _light: 'gray.50', _dark: 'whiteAlpha.200' },
}; };
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.300'); const borderColor = { _light: 'gray.200', _dark: 'whiteAlpha.300' };
return ( return (
<Box <Box
...@@ -49,19 +49,19 @@ const GasTrackerPriceSnippet = ({ data, type, isLoading }: Props) => { ...@@ -49,19 +49,19 @@ const GasTrackerPriceSnippet = ({ data, type, isLoading }: Props) => {
borderBottomWidth: { base: '2px', lg: '0' }, borderBottomWidth: { base: '2px', lg: '0' },
}} }}
> >
<Skeleton textStyle="h3" isLoaded={ !isLoading } w="fit-content">{ TITLES[type] }</Skeleton> <Skeleton loading={ isLoading } textStyle="heading.lg" w="fit-content">{ TITLES[type] }</Skeleton>
<Flex columnGap={ 3 } alignItems="center" mt={ 3 }> <Flex columnGap={ 3 } alignItems="center" mt={ 3 }>
<IconSvg name={ ICONS[type] } boxSize={{ base: '30px', xl: 10 }} isLoading={ isLoading } flexShrink={ 0 }/> <IconSvg name={ ICONS[type] } boxSize={{ base: '30px', xl: 10 }} isLoading={ isLoading } flexShrink={ 0 }/>
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
<GasPrice data={ data } fontSize={{ base: '36px', xl: '48px' }} lineHeight="48px" fontWeight={ 600 } letterSpacing="-1px" fontFamily="heading"/> <GasPrice data={ data } fontSize={{ base: '36px', xl: '48px' }} lineHeight="48px" fontWeight={ 600 } letterSpacing="-1px" fontFamily="heading"/>
</Skeleton> </Skeleton>
</Flex> </Flex>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary" mt={ 3 } w="fit-content"> <Skeleton loading={ isLoading } fontSize="sm" color="text_secondary" mt={ 3 } w="fit-content">
{ data.price !== null && data.fiat_price !== null && <GasPrice data={ data } prefix={ `${ asymp } ` } unitMode="secondary"/> } { data.price !== null && data.fiat_price !== null && <GasPrice data={ data } prefix={ `${ asymp } ` } unitMode="secondary"/> }
<span> per transaction</span> <span> per transaction</span>
{ typeof data.time === 'number' && data.time > 0 && <span> / { (data.time / SECOND).toLocaleString(undefined, { maximumFractionDigits: 1 }) }s</span> } { typeof data.time === 'number' && data.time > 0 && <span> / { (data.time / SECOND).toLocaleString(undefined, { maximumFractionDigits: 1 }) }s</span> }
</Skeleton> </Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary" mt={ 2 } w="fit-content" whiteSpace="pre"> <Skeleton loading={ isLoading } fontSize="sm" color="text_secondary" mt={ 2 } w="fit-content" whiteSpace="pre">
{ typeof data.base_fee === 'number' && <span>Base { data.base_fee.toLocaleString(undefined, { maximumFractionDigits: 0 }) }</span> } { typeof data.base_fee === 'number' && <span>Base { data.base_fee.toLocaleString(undefined, { maximumFractionDigits: 0 }) }</span> }
{ typeof data.base_fee === 'number' && typeof data.priority_fee === 'number' && <span> / </span> } { typeof data.base_fee === 'number' && typeof data.priority_fee === 'number' && <span> / </span> }
{ typeof data.priority_fee === 'number' && <span>Priority { data.priority_fee.toLocaleString(undefined, { maximumFractionDigits: 0 }) }</span> } { typeof data.priority_fee === 'number' && <span>Priority { data.priority_fee.toLocaleString(undefined, { maximumFractionDigits: 0 }) }</span> }
......
import { Flex, useColorModeValue } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { GasPrices } from 'types/api/stats'; import type { GasPrices } from 'types/api/stats';
...@@ -11,13 +11,11 @@ interface Props { ...@@ -11,13 +11,11 @@ interface Props {
} }
const GasTrackerPrices = ({ prices, isLoading }: Props) => { const GasTrackerPrices = ({ prices, isLoading }: Props) => {
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.300');
return ( return (
<Flex <Flex
as="ul" as="ul"
flexDir={{ base: 'column', lg: 'row' }} flexDir={{ base: 'column', lg: 'row' }}
borderColor={ borderColor } borderColor={{ _light: 'gray.200', _dark: 'whiteAlpha.300' }}
borderWidth="2px" borderWidth="2px"
borderRadius="xl" borderRadius="xl"
overflow="hidden" overflow="hidden"
......
import { import {
Alert,
Box, Box,
Flex, Flex,
Heading,
chakra, chakra,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -11,11 +9,13 @@ import config from 'configs/app'; ...@@ -11,11 +9,13 @@ import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import { HOMEPAGE_STATS } from 'stubs/stats'; import { HOMEPAGE_STATS } from 'stubs/stats';
import { Alert } from 'toolkit/chakra/alert';
import { Heading } from 'toolkit/chakra/heading';
import { Skeleton } from 'toolkit/chakra/skeleton';
import GasTrackerChart from 'ui/gasTracker/GasTrackerChart'; import GasTrackerChart from 'ui/gasTracker/GasTrackerChart';
import GasTrackerFaq from 'ui/gasTracker/GasTrackerFaq'; import GasTrackerFaq from 'ui/gasTracker/GasTrackerFaq';
import GasTrackerNetworkUtilization from 'ui/gasTracker/GasTrackerNetworkUtilization'; import GasTrackerNetworkUtilization from 'ui/gasTracker/GasTrackerNetworkUtilization';
import GasTrackerPrices from 'ui/gasTracker/GasTrackerPrices'; import GasTrackerPrices from 'ui/gasTracker/GasTrackerPrices';
import Skeleton from 'ui/shared/chakra/Skeleton';
import GasInfoUpdateTimer from 'ui/shared/gas/GasInfoUpdateTimer'; import GasInfoUpdateTimer from 'ui/shared/gas/GasInfoUpdateTimer';
import NativeTokenIcon from 'ui/shared/NativeTokenIcon'; import NativeTokenIcon from 'ui/shared/NativeTokenIcon';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -48,7 +48,7 @@ const GasTracker = () => { ...@@ -48,7 +48,7 @@ const GasTracker = () => {
{ typeof data?.network_utilization_percentage === 'number' && { typeof data?.network_utilization_percentage === 'number' &&
<GasTrackerNetworkUtilization percentage={ data.network_utilization_percentage } isLoading={ isLoading }/> } <GasTrackerNetworkUtilization percentage={ data.network_utilization_percentage } isLoading={ isLoading }/> }
{ data?.gas_price_updated_at && ( { data?.gas_price_updated_at && (
<Skeleton isLoaded={ !isLoading } whiteSpace="pre" display="flex" alignItems="center"> <Skeleton loading={ isLoading } whiteSpace="pre" display="flex" alignItems="center">
<span>Last updated </span> <span>Last updated </span>
<chakra.span color="text_secondary">{ dayjs(data.gas_price_updated_at).format('DD MMM, HH:mm:ss') }</chakra.span> <chakra.span color="text_secondary">{ dayjs(data.gas_price_updated_at).format('DD MMM, HH:mm:ss') }</chakra.span>
{ data.gas_prices_update_in !== 0 && ( { data.gas_prices_update_in !== 0 && (
...@@ -56,14 +56,14 @@ const GasTracker = () => { ...@@ -56,14 +56,14 @@ const GasTracker = () => {
key={ dataUpdatedAt } key={ dataUpdatedAt }
startTime={ dataUpdatedAt } startTime={ dataUpdatedAt }
duration={ data.gas_prices_update_in } duration={ data.gas_prices_update_in }
size={ 5 } size="md"
ml={ 2 } ml={ 2 }
/> />
) } ) }
</Skeleton> </Skeleton>
) } ) }
{ data?.coin_price && ( { data?.coin_price && (
<Skeleton isLoaded={ !isLoading } ml={{ base: 0, lg: 'auto' }} whiteSpace="pre" display="flex" alignItems="center"> <Skeleton loading={ isLoading } ml={{ base: 0, lg: 'auto' }} whiteSpace="pre" display="flex" alignItems="center">
<NativeTokenIcon mr={ 2 } boxSize={ 6 }/> <NativeTokenIcon mr={ 2 } boxSize={ 6 }/>
<chakra.span color="text_secondary">{ config.chain.currency.symbol }</chakra.span> <chakra.span color="text_secondary">{ config.chain.currency.symbol }</chakra.span>
<span> ${ Number(data.coin_price).toLocaleString(undefined, { maximumFractionDigits: 2 }) }</span> <span> ${ Number(data.coin_price).toLocaleString(undefined, { maximumFractionDigits: 2 }) }</span>
...@@ -89,7 +89,7 @@ const GasTracker = () => { ...@@ -89,7 +89,7 @@ const GasTracker = () => {
secondRow={ titleSecondRow } secondRow={ titleSecondRow }
withTextAd withTextAd
/> />
<Heading as="h2" mt={ 8 } mb={ 4 } fontSize="2xl">{ `Track ${ config.chain.name } gas fees` }</Heading> <Heading level="2" mt={ 8 } mb={ 4 }>{ `Track ${ config.chain.name } gas fees` }</Heading>
{ snippets } { snippets }
{ config.features.stats.isEnabled && ( { config.features.stats.isEnabled && (
<Box mt={ 12 }> <Box mt={ 12 }>
......
import type { ProgressCircle } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import type { ProgressCircleRootProps } from 'toolkit/chakra/progress-circle';
import { ProgressCircleRing, ProgressCircleRoot } from 'toolkit/chakra/progress-circle'; import { ProgressCircleRing, ProgressCircleRoot } from 'toolkit/chakra/progress-circle';
interface Props { interface Props extends ProgressCircleRootProps {
startTime: number; startTime: number;
duration: number; duration: number;
className?: string;
size?: ProgressCircle.RootProps['size'];
} }
const getValue = (startDate: dayjs.Dayjs, duration: number) => { const getValue = (startDate: dayjs.Dayjs, duration: number) => {
...@@ -24,7 +21,7 @@ const getValue = (startDate: dayjs.Dayjs, duration: number) => { ...@@ -24,7 +21,7 @@ const getValue = (startDate: dayjs.Dayjs, duration: number) => {
return value; return value;
}; };
const GasInfoUpdateTimer = ({ startTime, duration, className, size = 'sm' }: Props) => { const GasInfoUpdateTimer = ({ startTime, duration, size = 'sm', ...rest }: Props) => {
const [ value, setValue ] = React.useState(getValue(dayjs(startTime), duration)); const [ value, setValue ] = React.useState(getValue(dayjs(startTime), duration));
React.useEffect(() => { React.useEffect(() => {
...@@ -45,13 +42,13 @@ const GasInfoUpdateTimer = ({ startTime, duration, className, size = 'sm' }: Pro ...@@ -45,13 +42,13 @@ const GasInfoUpdateTimer = ({ startTime, duration, className, size = 'sm' }: Pro
return ( return (
<ProgressCircleRoot <ProgressCircleRoot
className={ className }
value={ value } value={ value }
size={ size } size={ size }
{ ...rest }
> >
<ProgressCircleRing/> <ProgressCircleRing/>
</ProgressCircleRoot> </ProgressCircleRoot>
); );
}; };
export default React.memo(chakra(GasInfoUpdateTimer)); export default React.memo(GasInfoUpdateTimer);
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