mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
Merge branch 'client-api-calls' into client-api-calls-pt2
This commit is contained in:
@@ -6,6 +6,7 @@ import type {
|
||||
AddressTransactionsResponse,
|
||||
AddressTokenTransferResponse,
|
||||
AddressCoinBalanceHistoryResponse,
|
||||
AddressCoinBalanceHistoryChart,
|
||||
AddressBlocksValidatedResponse,
|
||||
AddressInternalTxsResponse,
|
||||
AddressTxsFilters,
|
||||
@@ -149,6 +150,9 @@ export const RESOURCES = {
|
||||
paginationFields: [ 'items_count' as const, 'block_number' as const ],
|
||||
filterFields: [ ],
|
||||
},
|
||||
address_coin_balance_chart: {
|
||||
path: '/api/v2/addresses/:id/coin-balance-history-by-day',
|
||||
},
|
||||
|
||||
// HOMEPAGE
|
||||
homepage_stats: {
|
||||
@@ -244,6 +248,7 @@ Q extends 'address_internal_txs' ? AddressInternalTxsResponse :
|
||||
Q extends 'address_token_transfers' ? AddressTokenTransferResponse :
|
||||
Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse :
|
||||
Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse :
|
||||
Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart :
|
||||
Q extends 'config_json_rpc' ? JsonRpcUrlResponse :
|
||||
never;
|
||||
/* eslint-enable @typescript-eslint/indent */
|
||||
|
||||
@@ -80,6 +80,11 @@ export interface AddressCoinBalanceHistoryResponse {
|
||||
};
|
||||
}
|
||||
|
||||
export type AddressCoinBalanceHistoryChart = Array<{
|
||||
date: string;
|
||||
value: string;
|
||||
}>
|
||||
|
||||
export interface AddressBlocksValidatedResponse {
|
||||
items: Array<Block>;
|
||||
next_page_params: {
|
||||
|
||||
@@ -69,7 +69,7 @@ const AddressCoinBalance = ({ addressQuery }: Props) => {
|
||||
return (
|
||||
<>
|
||||
{ socketAlert && <SocketAlert mb={ 6 }/> }
|
||||
<AddressCoinBalanceChart/>
|
||||
<AddressCoinBalanceChart addressQuery={ addressQuery }/>
|
||||
<AddressCoinBalanceHistory query={ coinBalanceQuery }/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,36 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import type { UseQueryResult } from '@tanstack/react-query';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
const AddressCoinBalanceChart = () => {
|
||||
// chart will be added after stats feature is finalized
|
||||
return <Box p={ 4 } borderColor="gray.200" borderRadius="md" borderWidth="1px">Here will be coin balance chart</Box>;
|
||||
import type { Address } from 'types/api/address';
|
||||
|
||||
import appConfig from 'configs/app/config';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import ChartWidget from 'ui/shared/chart/ChartWidget';
|
||||
|
||||
interface Props {
|
||||
addressQuery: UseQueryResult<Address>;
|
||||
}
|
||||
|
||||
const AddressCoinBalanceChart = ({ addressQuery }: Props) => {
|
||||
const { data, isLoading, isError } = useApiQuery('address_coin_balance_chart', {
|
||||
pathParams: { id: addressQuery.data?.hash },
|
||||
queryOptions: { enabled: Boolean(addressQuery.data?.hash) },
|
||||
});
|
||||
|
||||
const items = React.useMemo(() => data?.map(({ date, value }) => ({
|
||||
date: new Date(date),
|
||||
value: BigNumber(value).div(10 ** appConfig.network.currency.decimals).toNumber(),
|
||||
})), [ data ]);
|
||||
|
||||
return (
|
||||
<ChartWidget
|
||||
chartHeight="200px"
|
||||
title="Balances"
|
||||
items={ items }
|
||||
isLoading={ isLoading || isError }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddressCoinBalanceChart;
|
||||
export default React.memo(AddressCoinBalanceChart);
|
||||
|
||||
@@ -16,13 +16,14 @@ import FullscreenChartModal from './FullscreenChartModal';
|
||||
type Props = {
|
||||
items?: Array<TimeChartItem>;
|
||||
title: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
isLoading: boolean;
|
||||
chartHeight?: string;
|
||||
}
|
||||
|
||||
const DOWNLOAD_IMAGE_SCALE = 5;
|
||||
|
||||
const ChartWidget = ({ items, title, description, isLoading }: Props) => {
|
||||
const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Props) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [ isFullscreen, setIsFullscreen ] = useState(false);
|
||||
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
|
||||
@@ -78,7 +79,7 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => {
|
||||
}, [ title ]);
|
||||
|
||||
if (isLoading) {
|
||||
return <ChartWidgetSkeleton/>;
|
||||
return <ChartWidgetSkeleton hasDescription={ Boolean(description) } chartHeight={ chartHeight }/>;
|
||||
}
|
||||
|
||||
if (items) {
|
||||
@@ -105,15 +106,17 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => {
|
||||
{ title }
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
mb={ 1 }
|
||||
gridColumn={ 1 }
|
||||
as="p"
|
||||
variant="secondary"
|
||||
fontSize="xs"
|
||||
>
|
||||
{ description }
|
||||
</Text>
|
||||
{ description && (
|
||||
<Text
|
||||
mb={ 1 }
|
||||
gridColumn={ 1 }
|
||||
as="p"
|
||||
variant="secondary"
|
||||
fontSize="xs"
|
||||
>
|
||||
{ description }
|
||||
</Text>
|
||||
) }
|
||||
|
||||
<Tooltip label="Reset zoom">
|
||||
<IconButton
|
||||
@@ -170,12 +173,14 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => {
|
||||
</Menu>
|
||||
</Grid>
|
||||
|
||||
<ChartWidgetGraph
|
||||
items={ items }
|
||||
onZoom={ handleZoom }
|
||||
isZoomResetInitial={ isZoomResetInitial }
|
||||
title={ title }
|
||||
/>
|
||||
<Box h={ chartHeight || 'auto' }>
|
||||
<ChartWidgetGraph
|
||||
items={ items }
|
||||
onZoom={ handleZoom }
|
||||
isZoomResetInitial={ isZoomResetInitial }
|
||||
title={ title }
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<FullscreenChartModal
|
||||
@@ -192,4 +197,4 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ChartWidget;
|
||||
export default React.memo(ChartWidget);
|
||||
|
||||
@@ -22,7 +22,7 @@ interface Props {
|
||||
isZoomResetInitial: boolean;
|
||||
}
|
||||
|
||||
const CHART_MARGIN = { bottom: 20, left: 30, right: 20, top: 10 };
|
||||
const CHART_MARGIN = { bottom: 20, left: 40, right: 20, top: 10 };
|
||||
|
||||
const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title }: Props) => {
|
||||
const isMobile = useIsMobile();
|
||||
@@ -31,10 +31,9 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
|
||||
const overlayRef = React.useRef<SVGRectElement>(null);
|
||||
const chartId = useMemo(() => `chart-${ title.split(' ').join('') }`, [ title ]);
|
||||
const [ range, setRange ] = React.useState<[ number, number ]>([ 0, Infinity ]);
|
||||
const { innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN);
|
||||
const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN);
|
||||
|
||||
const displayedData = useMemo(() => items.slice(range[0], range[1]).map((d) =>
|
||||
({ ...d, date: new Date(d.date) })), [ items, range ]);
|
||||
const displayedData = useMemo(() => items.slice(range[0], range[1]), [ items, range ]);
|
||||
const chartData = [ { items: items, name: 'Value', color } ];
|
||||
|
||||
const { yTickFormat, xScale, yScale } = useTimeChartController({
|
||||
@@ -55,9 +54,9 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
|
||||
}, [ isZoomResetInitial ]);
|
||||
|
||||
return (
|
||||
<svg width="100%" height="100%" ref={ ref } cursor="pointer" id={ chartId }>
|
||||
<svg width={ width || '100%' } height={ height || 'auto' } ref={ ref } cursor="pointer" id={ chartId } opacity={ width ? 1 : 0 }>
|
||||
|
||||
<g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` } opacity={ innerWidth ? 1 : 0 }>
|
||||
<g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` }>
|
||||
<ChartGridLine
|
||||
type="horizontal"
|
||||
scale={ yScale }
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { Box, Skeleton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
const ChartWidgetSkeleton = () => {
|
||||
interface Props {
|
||||
hasDescription: boolean;
|
||||
chartHeight?: string;
|
||||
}
|
||||
|
||||
const ChartWidgetSkeleton = ({ hasDescription, chartHeight }: Props) => {
|
||||
return (
|
||||
<Box
|
||||
height="235px"
|
||||
paddingY={{ base: 3, lg: 4 }}
|
||||
>
|
||||
<Skeleton w="75%" h="24px" mb={ 1 }/>
|
||||
<Skeleton w="50%" h="18px" mb={ 5 }/>
|
||||
<Skeleton w="75%" h="24px"/>
|
||||
{ hasDescription && <Skeleton w="50%" h="18px" mt={ 1 }/> }
|
||||
|
||||
<Skeleton w="100%" h="150px"/>
|
||||
<Skeleton w="100%" h={ chartHeight || '150px' } mt={ 5 }/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import ChartWidgetGraph from './ChartWidgetGraph';
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
items: Array<TimeChartItem>;
|
||||
onClose: () => void;
|
||||
}
|
||||
@@ -56,14 +56,16 @@ const FullscreenChartModal = ({
|
||||
{ title }
|
||||
</Heading>
|
||||
|
||||
<Text
|
||||
gridColumn={ 1 }
|
||||
as="p"
|
||||
variant="secondary"
|
||||
fontSize="xs"
|
||||
>
|
||||
{ description }
|
||||
</Text>
|
||||
{ description && (
|
||||
<Text
|
||||
gridColumn={ 1 }
|
||||
as="p"
|
||||
variant="secondary"
|
||||
fontSize="xs"
|
||||
>
|
||||
{ description }
|
||||
</Text>
|
||||
) }
|
||||
|
||||
{ !isZoomResetInitial && (
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user