Merge pull request #58 from vegaprotocol/feat/41-blocks-styling
Feat/41 blocks styling
This commit is contained in:
commit
c2e4f1d007
60
apps/explorer/src/app/components/blocks/block-data.tsx
Normal file
60
apps/explorer/src/app/components/blocks/block-data.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { BlockMeta } from '../../routes/blocks/tendermint-blockchain-response';
|
||||||
|
import { Routes } from '../../routes/router-config';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { SecondsAgo } from '../seconds-ago';
|
||||||
|
import { Table, TableRow, TableCell } from '../table';
|
||||||
|
|
||||||
|
interface BlockProps {
|
||||||
|
block: BlockMeta;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlockData = ({ block, className }: BlockProps) => {
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
aria-label={`Data for block ${block.header?.height}`}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<TableRow data-testid="block-row" modifier="background">
|
||||||
|
<TableCell
|
||||||
|
data-testid="block-height"
|
||||||
|
className="pl-4 py-2"
|
||||||
|
aria-label="Block height"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={`/${Routes.BLOCKS}/${block.header?.height}`}
|
||||||
|
className="text-vega-yellow"
|
||||||
|
>
|
||||||
|
{block.header?.height}
|
||||||
|
</Link>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
data-testid="num-txs"
|
||||||
|
className="px-8 text-center"
|
||||||
|
aria-label="Number of transactions"
|
||||||
|
>
|
||||||
|
{block.num_txs === '1'
|
||||||
|
? '1 transaction'
|
||||||
|
: `${block.num_txs} transactions`}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
data-testid="validator-link"
|
||||||
|
className="px-8 text-center"
|
||||||
|
aria-label="Validator"
|
||||||
|
>
|
||||||
|
<Link to={`/${Routes.VALIDATORS}`}>
|
||||||
|
{block.header.proposer_address}
|
||||||
|
</Link>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
data-testid="block-time"
|
||||||
|
className="text-center pr-28 text-neutral-300"
|
||||||
|
aria-label="Block genesis"
|
||||||
|
>
|
||||||
|
<SecondsAgo date={block.header?.time} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
29
apps/explorer/src/app/components/blocks/blocks-data.tsx
Normal file
29
apps/explorer/src/app/components/blocks/blocks-data.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TendermintBlockchainResponse } from '../../routes/blocks/tendermint-blockchain-response';
|
||||||
|
import { BlockData } from './block-data';
|
||||||
|
|
||||||
|
interface BlocksProps {
|
||||||
|
data: TendermintBlockchainResponse | undefined;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlocksData = ({ data, className }: BlocksProps) => {
|
||||||
|
if (!data?.result) {
|
||||||
|
return <div className={className}>Awaiting block data</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul
|
||||||
|
aria-label={`Showing ${data.result?.block_metas.length} most recently loaded blocks`}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{data.result?.block_metas?.map((block, index) => {
|
||||||
|
return (
|
||||||
|
<li key={index} data-testid="block-row">
|
||||||
|
<BlockData block={block} />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
15
apps/explorer/src/app/components/blocks/blocks-refetch.tsx
Normal file
15
apps/explorer/src/app/components/blocks/blocks-refetch.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
interface BlocksRefetchProps {
|
||||||
|
refetch: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlocksRefetch = ({ refetch }: BlocksRefetchProps) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => refetch()}
|
||||||
|
className="underline mb-28"
|
||||||
|
data-testid="refresh"
|
||||||
|
>
|
||||||
|
Refresh to see latest blocks
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
@ -1,53 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { TendermintBlockchainResponse } from '../../../routes/blocks/tendermint-blockchain-response';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { SecondsAgo } from '../../seconds-ago';
|
|
||||||
import { TxsPerBlock } from '../../txs/txs-per-block';
|
|
||||||
import { Table } from '../../table';
|
|
||||||
|
|
||||||
interface BlocksProps {
|
|
||||||
data: TendermintBlockchainResponse | undefined;
|
|
||||||
showTransactions?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BlocksTable = ({ data, showTransactions }: BlocksProps) => {
|
|
||||||
if (!data?.result) {
|
|
||||||
return <>No block data</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table>
|
|
||||||
{data.result?.block_metas?.map((block, index) => {
|
|
||||||
return (
|
|
||||||
<React.Fragment key={index}>
|
|
||||||
<tr data-testid="block-row">
|
|
||||||
<td data-testid="block-height">
|
|
||||||
<Link to={`/blocks/${block.header?.height}`}>
|
|
||||||
{block.header?.height}
|
|
||||||
</Link>
|
|
||||||
</td>
|
|
||||||
<td data-testid="num-txs">
|
|
||||||
{block.num_txs === '1'
|
|
||||||
? '1 transaction'
|
|
||||||
: `${block.num_txs} transactions`}
|
|
||||||
</td>
|
|
||||||
<td data-testid="validator-link">
|
|
||||||
<Link to={`/validators/${block.header?.proposer_address}`}>
|
|
||||||
{block.header.proposer_address}
|
|
||||||
</Link>
|
|
||||||
</td>
|
|
||||||
<td data-testid="block-time">
|
|
||||||
<SecondsAgo date={block.header?.time} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{showTransactions && (
|
|
||||||
<tr>
|
|
||||||
<TxsPerBlock blockHeight={block.header?.height} />
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1 +1,3 @@
|
|||||||
export { BlocksTable } from './home/blocks-table';
|
export { BlocksData } from './blocks-data';
|
||||||
|
export { BlockData } from './block-data';
|
||||||
|
export { BlocksRefetch } from './blocks-refetch';
|
||||||
|
@ -20,8 +20,24 @@ export const JumpToBlock = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<input type={'tel'} name={'blockNumber'} placeholder={'Block number'} />
|
<label
|
||||||
<input type={'submit'} value={'Go'} />
|
htmlFor="block-input"
|
||||||
|
className="block uppercase text-h5 font-bold"
|
||||||
|
>
|
||||||
|
Jump to block
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="block-input"
|
||||||
|
type="tel"
|
||||||
|
name={'blockNumber'}
|
||||||
|
placeholder={'Block number'}
|
||||||
|
className="bg-white-25 border-white border px-8 py-4 placeholder-white-60"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="border-white border px-28 py-4 cursor-pointer"
|
||||||
|
type={'submit'}
|
||||||
|
value={'Go'}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
26
apps/explorer/src/app/components/render-fetched/index.tsx
Normal file
26
apps/explorer/src/app/components/render-fetched/index.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { StatusMessage } from '../status-message';
|
||||||
|
|
||||||
|
interface RenderFetchedProps {
|
||||||
|
children: React.ReactElement;
|
||||||
|
error: Error | undefined;
|
||||||
|
loading: boolean | undefined;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RenderFetched = ({
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: RenderFetchedProps) => {
|
||||||
|
if (loading) {
|
||||||
|
return <StatusMessage className={className}>Loading...</StatusMessage>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <StatusMessage className={className}>Error: {error}</StatusMessage>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
};
|
19
apps/explorer/src/app/components/route-title/index.tsx
Normal file
19
apps/explorer/src/app/components/route-title/index.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface RouteTitleProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RouteTitle = ({ children, className }: RouteTitleProps) => {
|
||||||
|
const classes = classnames(
|
||||||
|
'font-alpha',
|
||||||
|
'text-h3',
|
||||||
|
'uppercase',
|
||||||
|
'mt-12',
|
||||||
|
'mb-28',
|
||||||
|
className
|
||||||
|
);
|
||||||
|
return <h1 className={classes}>{children}</h1>;
|
||||||
|
};
|
12
apps/explorer/src/app/components/status-message/index.tsx
Normal file
12
apps/explorer/src/app/components/status-message/index.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface StatusMessageProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusMessage = ({ children, className }: StatusMessageProps) => {
|
||||||
|
const classes = classnames('font-alpha text-h4 mb-28', className);
|
||||||
|
return <h3 className={classes}>{children}</h3>;
|
||||||
|
};
|
@ -1,13 +1,84 @@
|
|||||||
import React from 'react';
|
import React, { ThHTMLAttributes } from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
interface TableProps {
|
interface TableProps extends ThHTMLAttributes<HTMLTableElement> {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Table = ({ children }: TableProps) => {
|
interface TableHeaderProps
|
||||||
|
extends ThHTMLAttributes<HTMLTableHeaderCellElement> {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableRowProps extends ThHTMLAttributes<HTMLTableRowElement> {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
modifier?: 'bordered' | 'background';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableCellProps extends ThHTMLAttributes<HTMLTableCellElement> {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
modifier?: 'bordered' | 'background';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Table = ({ children, className, ...props }: TableProps) => {
|
||||||
|
const classes = classnames(className, 'overflow-x-auto whitespace-nowrap');
|
||||||
return (
|
return (
|
||||||
<table>
|
<div className={classes}>
|
||||||
<tbody>{children}</tbody>
|
<table className="w-full" {...props}>
|
||||||
</table>
|
<tbody>{children}</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TableHeader = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: TableHeaderProps) => {
|
||||||
|
const cellClasses = classnames(className, {
|
||||||
|
'text-left font-normal': props?.scope === 'row',
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<th className={cellClasses} {...props}>
|
||||||
|
{children}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TableRow = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
modifier,
|
||||||
|
...props
|
||||||
|
}: TableRowProps) => {
|
||||||
|
const cellClasses = classnames(className, {
|
||||||
|
'border-b border-white-40': modifier === 'bordered',
|
||||||
|
'bg-white-25 border-b-4 border-b-black': modifier === 'background',
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<tr className={cellClasses} {...props}>
|
||||||
|
{children}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TableCell = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
modifier,
|
||||||
|
...props
|
||||||
|
}: TableCellProps) => {
|
||||||
|
const cellClasses = classnames(className, {
|
||||||
|
'py-4': modifier === 'bordered',
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<td className={cellClasses} {...props}>
|
||||||
|
{children}
|
||||||
|
</td>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
54
apps/explorer/src/app/components/truncate/truncate.tsx
Normal file
54
apps/explorer/src/app/components/truncate/truncate.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
const ELLIPSIS = '\u2026';
|
||||||
|
|
||||||
|
interface TruncateInlineProps {
|
||||||
|
text: string | null;
|
||||||
|
className?: string;
|
||||||
|
children?: (truncatedText: string) => React.ReactElement;
|
||||||
|
startChars?: number; // number chars to show before ellipsis
|
||||||
|
endChars?: number; // number of chars to show after ellipsis
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncates a string of text from the center showing a specified number
|
||||||
|
* of characters from the start and end. Optionally takes a children as
|
||||||
|
* a render props so truncated text can be used inside other elements such
|
||||||
|
* as links
|
||||||
|
*/
|
||||||
|
export function TruncateInline({
|
||||||
|
text,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
startChars,
|
||||||
|
endChars,
|
||||||
|
}: TruncateInlineProps) {
|
||||||
|
if (text === null) {
|
||||||
|
return <span data-testid="empty-truncation" />;
|
||||||
|
}
|
||||||
|
const truncatedText = truncateByChars(text, startChars, endChars);
|
||||||
|
|
||||||
|
const wrapperProps = {
|
||||||
|
title: text,
|
||||||
|
className,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (children !== undefined) {
|
||||||
|
return <span {...wrapperProps}>{children(truncatedText)}</span>;
|
||||||
|
} else {
|
||||||
|
return <span {...wrapperProps}>{truncatedText}</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function truncateByChars(s: string, startChars = 6, endChars = 6) {
|
||||||
|
// if the text is shorted than the total number of chars to show
|
||||||
|
// no truncation is needed. Plus one is to account for the ellipsis
|
||||||
|
if (s.length <= startChars + endChars + 1) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = s.slice(0, startChars);
|
||||||
|
const end = s.slice(-endChars);
|
||||||
|
|
||||||
|
return start + ELLIPSIS + end;
|
||||||
|
}
|
34
apps/explorer/src/app/components/txs/home/block-txs-data.tsx
Normal file
34
apps/explorer/src/app/components/txs/home/block-txs-data.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { TendermintBlockchainResponse } from '../../../routes/blocks/tendermint-blockchain-response';
|
||||||
|
import { BlockData } from '../../blocks';
|
||||||
|
import { TxsPerBlock } from '../txs-per-block';
|
||||||
|
|
||||||
|
interface TxsProps {
|
||||||
|
data: TendermintBlockchainResponse | undefined;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlockTxsData = ({ data, className }: TxsProps) => {
|
||||||
|
if (!data?.result) {
|
||||||
|
// Data for the block has already been fetched at this point, so no errors
|
||||||
|
// or loading to deal with. This is specifically the case
|
||||||
|
// where the data object is not undefined, but lacks a result.
|
||||||
|
return <div className={className}>No data</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul
|
||||||
|
aria-label={`Showing ${data.result?.block_metas.length} most recently loaded blocks and transactions`}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{data.result?.block_metas?.map((block, index) => {
|
||||||
|
return (
|
||||||
|
<li key={index} data-testid="block-row">
|
||||||
|
<BlockData block={block} className="mb-12" />
|
||||||
|
<TxsPerBlock blockHeight={block.header.height} />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Routes } from '../../../routes/router-config';
|
import { Routes } from '../../../routes/router-config';
|
||||||
import { Result } from '../../../routes/txs/tendermint-transaction-response.d';
|
import { Result } from '../../../routes/txs/tendermint-transaction-response.d';
|
||||||
import { Table } from '../../table';
|
import { Table, TableRow, TableCell } from '../../table';
|
||||||
|
|
||||||
interface TxDetailsProps {
|
interface TxDetailsProps {
|
||||||
txData: Result | undefined;
|
txData: Result | undefined;
|
||||||
@ -15,35 +15,37 @@ export const TxDetails = ({ txData, pubKey }: TxDetailsProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
<tr>
|
<TableRow>
|
||||||
<td>Hash</td>
|
<TableCell>Hash</TableCell>
|
||||||
<td data-testid="hash">{txData.hash}</td>
|
<TableCell data-testid="hash">{txData.hash}</TableCell>
|
||||||
</tr>
|
</TableRow>
|
||||||
{pubKey ? (
|
{pubKey ? (
|
||||||
<tr>
|
<TableRow>
|
||||||
<td>Submitted by</td>
|
<td>Submitted by</td>
|
||||||
<td data-testid="submitted-by">
|
<td data-testid="submitted-by">
|
||||||
<Link to={`/${Routes.PARTIES}/${pubKey}`}>{pubKey}</Link>
|
<Link to={`/${Routes.PARTIES}/${pubKey}`}>{pubKey}</Link>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</TableRow>
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<TableRow>
|
||||||
<td>Submitted by</td>
|
<td>Submitted by</td>
|
||||||
<td>Awaiting decoded transaction data</td>
|
<td>Awaiting decoded transaction data</td>
|
||||||
</tr>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
{txData.height ? (
|
{txData.height ? (
|
||||||
<tr>
|
<TableRow>
|
||||||
<td>Block</td>
|
<td>Block</td>
|
||||||
<td data-testid="block">
|
<td data-testid="block">
|
||||||
<Link to={`/blocks/${txData.height}`}>{txData.height}</Link>
|
<Link to={`/${Routes.BLOCKS}/${txData.height}`}>
|
||||||
|
{txData.height}
|
||||||
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</TableRow>
|
||||||
) : null}
|
) : null}
|
||||||
<tr>
|
<TableRow>
|
||||||
<td>Encoded tnx</td>
|
<td>Encoded tnx</td>
|
||||||
<td data-testid="encoded-tnx">{txData.tx}</td>
|
<td data-testid="encoded-tnx">{txData.tx}</td>
|
||||||
</tr>
|
</TableRow>
|
||||||
</Table>
|
</Table>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export { TxDetails } from './id/tx-details';
|
export { TxDetails } from './id/tx-details';
|
||||||
export { TxContent } from './id/tx-content';
|
export { TxContent } from './id/tx-content';
|
||||||
export { TxList } from './home/tx-list';
|
export { TxList } from './pending/tx-list';
|
||||||
|
export { BlockTxsData } from './home/block-txs-data';
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
import useFetch from '../../../hooks/use-fetch';
|
import useFetch from '../../../hooks/use-fetch';
|
||||||
import { ChainExplorerTxResponse } from '../../../routes/types/chain-explorer-response';
|
import { ChainExplorerTxResponse } from '../../../routes/types/chain-explorer-response';
|
||||||
|
import { Routes } from '../../../routes/router-config';
|
||||||
import { DATA_SOURCES } from '../../../config';
|
import { DATA_SOURCES } from '../../../config';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { RenderFetched } from '../../render-fetched';
|
||||||
|
import { TruncateInline } from '../../truncate/truncate';
|
||||||
|
|
||||||
interface TxsPerBlockProps {
|
interface TxsPerBlockProps {
|
||||||
blockHeight: string | undefined;
|
blockHeight: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const truncateLength = 14;
|
||||||
|
|
||||||
export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
|
export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
|
||||||
const {
|
const {
|
||||||
state: { data: decodedBlockData },
|
state: { data: decodedBlockData, loading, error },
|
||||||
} = useFetch<ChainExplorerTxResponse[]>(DATA_SOURCES.chainExplorerUrl, {
|
} = useFetch<ChainExplorerTxResponse[]>(DATA_SOURCES.chainExplorerUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
@ -24,28 +29,46 @@ export const TxsPerBlock = ({ blockHeight }: TxsPerBlockProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table>
|
<RenderFetched error={error} loading={loading} className="text-body-large">
|
||||||
<thead>
|
<div className="overflow-x-auto whitespace-nowrap mb-28">
|
||||||
<tr>
|
<table className="w-full">
|
||||||
<td>Transaction</td>
|
<thead>
|
||||||
<td>From</td>
|
<tr className="font-mono">
|
||||||
<td>Type</td>
|
<td>Transaction</td>
|
||||||
</tr>
|
<td>From</td>
|
||||||
</thead>
|
<td>Type</td>
|
||||||
<tbody>
|
</tr>
|
||||||
{decodedBlockData &&
|
</thead>
|
||||||
decodedBlockData.map(({ TxHash, PubKey, Type }, index) => {
|
<tbody>
|
||||||
return (
|
{decodedBlockData &&
|
||||||
<tr key={index}>
|
decodedBlockData.map(({ TxHash, PubKey, Type }) => {
|
||||||
<td>
|
return (
|
||||||
<Link to={`/txs/${TxHash}`}>{TxHash}</Link>
|
<tr key={TxHash}>
|
||||||
</td>
|
<td>
|
||||||
<td>{PubKey}</td>
|
<Link to={`/${Routes.TX}/${TxHash}`}>
|
||||||
<td>{Type}</td>
|
<TruncateInline
|
||||||
</tr>
|
text={TxHash}
|
||||||
);
|
startChars={truncateLength}
|
||||||
})}
|
endChars={truncateLength}
|
||||||
</tbody>
|
className="text-vega-yellow font-mono"
|
||||||
</table>
|
/>
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<TruncateInline
|
||||||
|
text={PubKey}
|
||||||
|
startChars={truncateLength}
|
||||||
|
endChars={truncateLength}
|
||||||
|
className="font-mono"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{Type}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</RenderFetched>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,29 +1,30 @@
|
|||||||
import { DATA_SOURCES } from '../../../config';
|
import { DATA_SOURCES } from '../../../config';
|
||||||
import useFetch from '../../../hooks/use-fetch';
|
import useFetch from '../../../hooks/use-fetch';
|
||||||
import { TendermintBlockchainResponse } from '../tendermint-blockchain-response';
|
import { TendermintBlockchainResponse } from '../tendermint-blockchain-response';
|
||||||
import { BlocksTable } from '../../../components/blocks';
|
import { RouteTitle } from '../../../components/route-title';
|
||||||
|
import { RenderFetched } from '../../../components/render-fetched';
|
||||||
|
import { BlocksData, BlocksRefetch } from '../../../components/blocks';
|
||||||
import { JumpToBlock } from '../../../components/jump-to-block';
|
import { JumpToBlock } from '../../../components/jump-to-block';
|
||||||
|
|
||||||
const Blocks = () => {
|
const Blocks = () => {
|
||||||
const {
|
const {
|
||||||
state: { data },
|
state: { data, error, loading },
|
||||||
refetch,
|
refetch,
|
||||||
} = useFetch<TendermintBlockchainResponse>(
|
} = useFetch<TendermintBlockchainResponse>(
|
||||||
`${DATA_SOURCES.tendermintUrl}/blockchain`
|
`${DATA_SOURCES.tendermintUrl}/blockchain`
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<section>
|
||||||
<section>
|
<RouteTitle>Blocks</RouteTitle>
|
||||||
<h1>Blocks</h1>
|
<RenderFetched error={error} loading={loading}>
|
||||||
<button data-testid="refresh" onClick={() => refetch()}>
|
<>
|
||||||
Refresh to see latest blocks
|
<BlocksRefetch refetch={refetch} />
|
||||||
</button>
|
<BlocksData data={data} className="mb-28" />
|
||||||
<BlocksTable data={data} />
|
</>
|
||||||
</section>
|
</RenderFetched>
|
||||||
|
|
||||||
<JumpToBlock />
|
<JumpToBlock />
|
||||||
</>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,9 +3,15 @@ import { Link, useParams } from 'react-router-dom';
|
|||||||
import { DATA_SOURCES } from '../../../config';
|
import { DATA_SOURCES } from '../../../config';
|
||||||
import useFetch from '../../../hooks/use-fetch';
|
import useFetch from '../../../hooks/use-fetch';
|
||||||
import { TendermintBlocksResponse } from '../tendermint-blocks-response';
|
import { TendermintBlocksResponse } from '../tendermint-blocks-response';
|
||||||
|
import { RouteTitle } from '../../../components/route-title';
|
||||||
import { TxsPerBlock } from '../../../components/txs/txs-per-block';
|
import { TxsPerBlock } from '../../../components/txs/txs-per-block';
|
||||||
import { SecondsAgo } from '../../../components/seconds-ago';
|
import { SecondsAgo } from '../../../components/seconds-ago';
|
||||||
import { Table } from '../../../components/table';
|
import {
|
||||||
|
Table,
|
||||||
|
TableRow,
|
||||||
|
TableHeader,
|
||||||
|
TableCell,
|
||||||
|
} from '../../../components/table';
|
||||||
|
|
||||||
const Block = () => {
|
const Block = () => {
|
||||||
const { block } = useParams<{ block: string }>();
|
const { block } = useParams<{ block: string }>();
|
||||||
@ -23,24 +29,29 @@ const Block = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h1>BLOCK {block}</h1>
|
<RouteTitle>BLOCK {block}</RouteTitle>
|
||||||
<Table>
|
<Table className="mb-28">
|
||||||
<tr>
|
<TableRow modifier="bordered">
|
||||||
<td>Mined by</td>
|
<TableHeader scope="row">Mined by</TableHeader>
|
||||||
<td>
|
<TableCell modifier="bordered">
|
||||||
<Link to={`/validators/${header.proposer_address}`}>
|
<Link
|
||||||
|
className="text-vega-yellow"
|
||||||
|
to={"/validators"}
|
||||||
|
>
|
||||||
{header.proposer_address}
|
{header.proposer_address}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</TableCell>
|
||||||
</tr>
|
</TableRow>
|
||||||
<tr>
|
<TableRow modifier="bordered">
|
||||||
<td>Time</td>
|
<TableHeader scope="row">Time</TableHeader>
|
||||||
<td>
|
<TableCell modifier="bordered">
|
||||||
<SecondsAgo date={header.time} />
|
<SecondsAgo date={header.time} />
|
||||||
</td>
|
</TableCell>
|
||||||
</tr>
|
</TableRow>
|
||||||
</Table>
|
</Table>
|
||||||
<TxsPerBlock blockHeight={block} />
|
{blockData?.result.block.data.txs.length > 0 && (
|
||||||
|
<TxsPerBlock blockHeight={block} />
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,29 +1,31 @@
|
|||||||
import useFetch from '../../../hooks/use-fetch';
|
import useFetch from '../../../hooks/use-fetch';
|
||||||
import { TendermintBlockchainResponse } from '../../blocks/tendermint-blockchain-response';
|
import { TendermintBlockchainResponse } from '../../blocks/tendermint-blockchain-response';
|
||||||
import { DATA_SOURCES } from '../../../config';
|
import { DATA_SOURCES } from '../../../config';
|
||||||
import { BlocksTable } from '../../../components/blocks';
|
import { RouteTitle } from '../../../components/route-title';
|
||||||
|
import { BlocksRefetch } from '../../../components/blocks';
|
||||||
|
import { RenderFetched } from '../../../components/render-fetched';
|
||||||
|
import { BlockTxsData } from '../../../components/txs';
|
||||||
import { JumpToBlock } from '../../../components/jump-to-block';
|
import { JumpToBlock } from '../../../components/jump-to-block';
|
||||||
|
|
||||||
const Txs = () => {
|
const Txs = () => {
|
||||||
const {
|
const {
|
||||||
state: { data },
|
state: { data, error, loading },
|
||||||
refetch,
|
refetch,
|
||||||
} = useFetch<TendermintBlockchainResponse>(
|
} = useFetch<TendermintBlockchainResponse>(
|
||||||
`${DATA_SOURCES.tendermintUrl}/blockchain`
|
`${DATA_SOURCES.tendermintUrl}/blockchain`
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<section>
|
||||||
<section>
|
<RouteTitle>Transactions</RouteTitle>
|
||||||
<h1>Transactions</h1>
|
<RenderFetched error={error} loading={loading}>
|
||||||
<button data-testid="refresh" onClick={() => refetch()}>
|
<>
|
||||||
Refresh to see latest blocks
|
<BlocksRefetch refetch={refetch} />
|
||||||
</button>
|
<BlockTxsData data={data} />
|
||||||
<BlocksTable data={data} showTransactions={true} />
|
</>
|
||||||
</section>
|
</RenderFetched>
|
||||||
|
|
||||||
<JumpToBlock />
|
<JumpToBlock />
|
||||||
</>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user