Commit 8fb28abe authored by tom's avatar tom

gas tracker page

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