Commit 4b497063 authored by tom's avatar tom Committed by isstuev

data fetching and basic chart

parent d9c47068
import handler from 'lib/api/handler';
const getUrl = () => '/v2/stats/charts/market';
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
import handler from 'lib/api/handler';
const getUrl = () => '/v2/stats/charts/transactions';
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
export interface ChartTransactionItem {
date: string;
tx_count: number;
}
export interface ChartMarketItem {
date: string;
closing_price: string;
}
export interface ChartTransactionResponse {
chart_data: Array<ChartTransactionItem>;
}
export interface ChartMarketResponse {
available_supply: string;
chart_data: Array<ChartMarketItem>;
}
......@@ -11,4 +11,6 @@ export enum QueryKeys {
blockTxs = 'block-transactions',
block = 'block',
blocks = 'blocks',
chartsTxs = 'charts-txs',
chartsMarket = 'charts-market',
}
......@@ -5,7 +5,7 @@ import ethTxsData from 'data/charts_eth_txs.json';
import ChartLine from 'ui/shared/chart/ChartLine';
import useChartSize from 'ui/shared/chart/useChartSize';
import useTimeChartController from 'ui/shared/chart/useTimeChartController';
import { BlueLinearGradient } from 'ui/shared/chart/utils/gradients';
import { BlueLineGradient } from 'ui/shared/chart/utils/gradients';
const CHART_MARGIN = { bottom: 0, left: 0, right: 0, top: 0 };
const DATA = ethTxsData.slice(-30).map((d) => ({ ...d, date: new Date(d.date) }));
......@@ -23,13 +23,13 @@ const SplineChartExample = () => {
return (
<svg width={ width || '100%' } height={ height || '100%' } ref={ ref }>
<defs>
<BlueLinearGradient.defs/>
<BlueLineGradient.defs/>
</defs>
<ChartLine
data={ DATA }
xScale={ xScale }
yScale={ yScale }
stroke={ `url(#${ BlueLinearGradient.id })` }
stroke={ `url(#${ BlueLineGradient.id })` }
animation="left"
strokeWidth={ 3 }
/>
......
import { Box } from '@chakra-ui/react';
import { useToken } from '@chakra-ui/react';
import React from 'react';
const ChainIndicatorChart = () => {
return <Box bgColor="orange.100" minH="300px"/>;
import type { ChainIndicatorChartData } from './types';
import ChartArea from 'ui/shared/chart/ChartArea';
import ChartLine from 'ui/shared/chart/ChartLine';
import useChartSize from 'ui/shared/chart/useChartSize';
import useTimeChartController from 'ui/shared/chart/useTimeChartController';
import { BlueLineGradient } from 'ui/shared/chart/utils/gradients';
interface Props {
data: ChainIndicatorChartData;
}
const ChainIndicatorChart = ({ data }: Props) => {
const ref = React.useRef<SVGSVGElement>(null);
const { width, height, innerWidth, innerHeight } = useChartSize(ref.current);
const color = useToken('colors', 'blue.300');
const { xScale, yScale } = useTimeChartController({
data: [ { items: data, name: 'spline' } ],
width: innerWidth,
height: innerHeight,
});
return (
<svg width={ width || '100%' } height={ height || '100%' } ref={ ref }>
<defs>
<BlueLineGradient.defs/>
</defs>
<ChartArea
data={ data }
color={ color }
xScale={ xScale }
yScale={ yScale }
/>
<ChartLine
data={ data }
xScale={ xScale }
yScale={ yScale }
stroke={ `url(#${ BlueLineGradient.id })` }
animation="left"
strokeWidth={ 3 }
/>
</svg>
);
};
export default ChainIndicatorChart;
export default React.memo(ChainIndicatorChart);
import { Box } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { ChainIndicatorChartData } from './types';
import ChainIndicatorChart from './ChainIndicatorChart';
type Props = UseQueryResult<ChainIndicatorChartData>;
const ChainIndicatorChartContainer = ({ data, isError, isLoading }: Props) => {
const content = (() => {
if (isLoading) {
return 'loading...';
}
if (isError) {
return 'error';
}
return <ChainIndicatorChart data={ data }/>;
})();
return <Box h="270px">{ content }</Box>;
};
export default React.memo(ChainIndicatorChartContainer);
import { Text, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import type { ChainIndicatorId } from './types';
interface Props {
name: string;
id: ChainIndicatorId;
title: string;
value: string;
isSelected: boolean;
icon: React.ReactNode;
onClick: (name: string) => void;
isSelected: boolean;
onClick: (id: ChainIndicatorId) => void;
}
const ChainIndicatorItem = ({ name, value, icon, isSelected, onClick }: Props) => {
const ChainIndicatorItem = ({ id, title, value, icon, isSelected, onClick }: Props) => {
const bgColor = useColorModeValue('white', 'gray.900');
const handleClick = React.useCallback(() => {
onClick(name);
}, [ name, onClick ]);
onClick(id);
}, [ id, onClick ]);
return (
<Flex
......@@ -35,7 +38,7 @@ const ChainIndicatorItem = ({ name, value, icon, isSelected, onClick }: Props) =
>
{ icon }
<Box>
<Text fontFamily="Poppins" fontWeight={ 500 }>{ name }</Text>
<Text fontFamily="Poppins" fontWeight={ 500 }>{ title }</Text>
<Text variant="secondary" fontWeight={ 600 }>{ value }</Text>
</Box>
</Flex>
......
import { Box, Flex, Icon, Text, Tooltip, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import appConfig from 'configs/app/config';
import globeIcon from 'icons/globe.svg';
import infoIcon from 'icons/info.svg';
import txIcon from 'icons/transactions.svg';
import ChainIndicatorChart from './ChainIndicatorChart';
import ChainIndicatorChartContainer from './ChainIndicatorChartContainer';
import ChainIndicatorItem from './ChainIndicatorItem';
const INDICATORS = [
{
name: 'Daily transactions',
value: '1,531.14 M',
icon: <Icon as={ txIcon } boxSize={ 6 } bgColor="#56ACD1" borderRadius="base" color="white"/>,
hint: `The total daily number of transactions on the blockchain for the last month.`,
},
{
name: `${ appConfig.network.currency.symbol } price`,
value: '$0.998566',
// todo_tom change to TokenLogo after token-transfers branch merge
icon: <Icon as={ globeIcon } boxSize={ 6 } bgColor="#6A5DCC" borderRadius="base" color="white"/>,
hint: `${ appConfig.network.currency.symbol } daily price in USD.`,
},
{
name: 'Market cap',
value: '$379M',
icon: <Icon as={ globeIcon } boxSize={ 6 } bgColor="#6A5DCC" borderRadius="base" color="white"/>,
// eslint-disable-next-line max-len
hint: 'The total market value of a cryptocurrency\'s circulating supply. It is analogous to the free-float capitalization in the stock market. Market Cap = Current Price x Circulating Supply.',
},
];
import useFetchChartData from './useFetchChartData';
import INDICATORS from './utils/indicators';
const ChainIndicators = () => {
const [ selectedIndicator, selectIndicator ] = React.useState(INDICATORS[0].name);
const [ selectedIndicator, selectIndicator ] = React.useState(INDICATORS[0].id);
const indicator = INDICATORS.find(({ id }) => id === selectedIndicator);
const queryResult = useFetchChartData(indicator);
const bgColor = useColorModeValue('white', 'gray.900');
const listBgColor = useColorModeValue('gray.50', 'black');
const indicator = INDICATORS.find(({ name }) => name === selectedIndicator);
return (
<Flex p={ 8 } borderRadius="lg" boxShadow="lg" bgColor={ bgColor } columnGap={ 12 } w="100%" alignItems="flex-start">
<Flex flexGrow={ 1 } flexDir="column">
<Flex alignItems="center">
<Text fontWeight={ 500 } fontFamily="Poppins" fontSize="lg">{ indicator?.name }</Text>
<Text fontWeight={ 500 } fontFamily="Poppins" fontSize="lg">{ indicator?.title }</Text>
{ indicator?.hint && (
<Tooltip label={ indicator.hint } maxW="300px">
<Box display="inline-flex" cursor="pointer" ml={ 1 }>
......@@ -54,14 +30,14 @@ const ChainIndicators = () => {
) }
</Flex>
<Text fontWeight={ 600 } fontFamily="Poppins" fontSize="48px" lineHeight="48px" mt={ 3 } mb={ 4 }>{ indicator?.value }</Text>
<ChainIndicatorChart/>
<ChainIndicatorChartContainer { ...queryResult }/>
</Flex>
<Flex flexShrink={ 0 } flexDir="column" as="ul" p={ 3 } borderRadius="lg" bgColor={ listBgColor } rowGap={ 3 }>
{ INDICATORS.map((indicator) => (
<ChainIndicatorItem
key={ indicator.name }
key={ indicator.id }
{ ...indicator }
isSelected={ selectedIndicator === indicator.name }
isSelected={ selectedIndicator === indicator.id }
onClick={ selectIndicator }
/>
)) }
......
import type { ChartTransactionResponse, ChartMarketResponse } from 'types/api/charts';
import type { QueryKeys } from 'types/client/queries';
import type { TimeChartItem } from 'ui/shared/chart/types';
export type ChartsQueryKeys = QueryKeys.chartsTxs | QueryKeys.chartsMarket;
export type ChainIndicatorId = 'daily_txs' | 'coin_price' | 'market_cup';
export interface TChainIndicator<Q extends ChartsQueryKeys> {
id: ChainIndicatorId;
title: string;
value: string;
icon: React.ReactNode;
hint?: string;
api: {
queryName: Q;
path: string;
dataFn: (response: ChartsResponse<Q>) => ChainIndicatorChartData;
};
}
export type ChartsResponse<Q extends ChartsQueryKeys> =
Q extends QueryKeys.chartsTxs ? ChartTransactionResponse :
Q extends QueryKeys.chartsMarket ? ChartMarketResponse :
never;
export type ChainIndicatorChartData = Array<TimeChartItem>;
import { useQuery } from '@tanstack/react-query';
import type { TChainIndicator, ChartsResponse, ChartsQueryKeys, ChainIndicatorChartData } from './types';
import useFetch from 'lib/hooks/useFetch';
type NotUndefined<T> = T extends undefined ? never : T;
export default function useFetchCharData<Q extends ChartsQueryKeys>(indicator: TChainIndicator<Q> | undefined) {
const fetch = useFetch();
type ResponseType = ChartsResponse<NotUndefined<typeof indicator>['api']['queryName']>;
return useQuery<unknown, unknown, ChainIndicatorChartData>(
[ indicator?.api.queryName ],
() => {
return fetch<ResponseType, unknown>(indicator?.api.path || '')
.then((result) => {
if ('status' in result) {
return Promise.reject(result);
}
return indicator?.api.dataFn(result);
});
},
{ enabled: Boolean(indicator) },
);
}
import { Icon } from '@chakra-ui/react';
import React from 'react';
import type { TChainIndicator } from '../types';
import { QueryKeys } from 'types/client/queries';
import appConfig from 'configs/app/config';
import globeIcon from 'icons/globe.svg';
import txIcon from 'icons/transactions.svg';
import { sortByDateDesc } from 'ui/shared/chart/utils/sorts';
const dailyTxsIndicator: TChainIndicator<QueryKeys.chartsTxs> = {
id: 'daily_txs',
title: 'Daily transactions',
value: '1,531.14 M',
icon: <Icon as={ txIcon } boxSize={ 6 } bgColor="#56ACD1" borderRadius="base" color="white"/>,
hint: `The total daily number of transactions on the blockchain for the last month.`,
api: {
queryName: QueryKeys.chartsTxs,
path: '/node-api/stats/charts/transactions',
dataFn: (response) => response.chart_data
.map((item) => ({ date: new Date(item.date), value: item.tx_count }))
.sort(sortByDateDesc),
},
};
const coinPriceIndicator: TChainIndicator<QueryKeys.chartsMarket> = {
id: 'coin_price',
title: `${ appConfig.network.currency.symbol } price`,
value: '$0.998566',
// todo_tom change to TokenLogo after token-transfers branch merge
icon: <Icon as={ globeIcon } boxSize={ 6 } bgColor="#6A5DCC" borderRadius="base" color="white"/>,
hint: `${ appConfig.network.currency.symbol } daily price in USD.`,
api: {
queryName: QueryKeys.chartsMarket,
path: '/node-api/stats/charts/market',
dataFn: (response) => response.chart_data
.map((item) => ({ date: new Date(item.date), value: Number(item.closing_price) * Number(response.available_supply) }))
.sort(sortByDateDesc),
},
};
const marketPriceIndicator: TChainIndicator<QueryKeys.chartsMarket> = {
id: 'market_cup',
title: 'Market cap',
value: '$379M',
icon: <Icon as={ globeIcon } boxSize={ 6 } bgColor="#6A5DCC" borderRadius="base" color="white"/>,
// eslint-disable-next-line max-len
hint: 'The total market value of a cryptocurrency\'s circulating supply. It is analogous to the free-float capitalization in the stock market. Market Cap = Current Price x Circulating Supply.',
api: {
queryName: QueryKeys.chartsMarket,
path: '/node-api/stats/charts/market',
dataFn: (response) => response.chart_data
.map((item) => ({ date: new Date(item.date), value: Number(item.closing_price) }))
.sort(sortByDateDesc),
},
};
const INDICATORS = [
dailyTxsIndicator,
coinPriceIndicator,
marketPriceIndicator,
];
export default INDICATORS;
import React from 'react';
export const BlueLinearGradient = {
export const BlueLineGradient = {
id: 'blue-linear-gradient',
defs: () => (
<linearGradient id="blue-linear-gradient">
......
import type { TimeChartItem } from '../types';
export const sortByDateDesc = (a: TimeChartItem, b: TimeChartItem) => {
return a.date.getTime() - b.date.getTime();
};
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