Skip to content

[Insight] Chain details page #3974

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 27 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0e6c23f
create chain page and added header with link to blocks
MicahMaphet Jun 28, 2025
8ff3057
Merge branch 'master' into chain-details
MicahMaphet Jul 8, 2025
0150419
added block list to chain page
MicahMaphet Jul 8, 2025
56ba11e
[REF] replace oneinch with coingecko for token lists
gabrielbazan7 Jul 7, 2025
180c2a1
v10.10.6
nitsujlangston Jul 8, 2025
6bcac67
prevent redefine _getX/Y
Sayrix Jul 1, 2025
ce76c31
verify prePublishRaw tx
leolambo Jul 3, 2025
6ccb7d9
Adding sourceAddress for SPL proposals
leolambo Jul 3, 2025
46ebbd7
add all recipients by default
leolambo Jul 3, 2025
b324db6
feedback
leolambo Jul 3, 2025
8c2b6bb
v10.10.7
nitsujlangston Jul 21, 2025
c5506ee
feat(templates): removes horizontal rule from email
mahume Jul 15, 2025
9a891a6
[FIX] select inputs when replaceTxByFee - reuse one original input
gabrielbazan7 Jul 21, 2025
f77b083
removed unused lodash imports
MicahMaphet Jul 14, 2025
e5dcf28
v10.10.8
nitsujlangston Jul 22, 2025
cd2a8ae
add feePerKb to Solana getFee
leolambo Jul 25, 2025
06bee9f
add solana base fee to defaults
leolambo Jul 25, 2025
3814bee
lint
leolambo Jul 25, 2025
2f8941f
v10.10.9
nitsujlangston Jul 25, 2025
1ae8b3b
Merge branch 'master' of https://github.com/bitpay/bitcore into chain…
MicahMaphet Jul 29, 2025
ec13fa5
moved block list (now 10 blocks) to the right and linting
MicahMaphet Jul 29, 2025
fc181a1
Merge branch 'fee-data' into chain-details
MicahMaphet Jul 29, 2025
aa43033
added view blocks buton to bottom of transactions and fee data block
MicahMaphet Jul 29, 2025
158b008
replaced table with block chain
MicahMaphet Jul 29, 2025
09435bd
added fee chart
MicahMaphet Aug 7, 2025
cf28776
refactored chain-details page and added block tip fee
MicahMaphet Aug 8, 2025
b332934
Merge branch 'master' of https://github.com/bitpay/bitcore into chain…
MicahMaphet Aug 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/insight/src/Routing.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {lazy, Suspense} from 'react';
import {Navigate, Route, Routes} from 'react-router-dom';
import Home from './pages';
import Chain from './pages/chain';
const Blocks = lazy(() => import('./pages/blocks'));
const Block = lazy(() => import('./pages/block'));
const TransactionHash = lazy(() => import('./pages/transaction'));
Expand All @@ -12,6 +13,7 @@ function Routing() {
<Suspense>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/:currency/:network/chain' element={<Chain />} />
<Route path='/:currency/:network/blocks' element={<Blocks />} />
<Route path='/:currency/:network/block/:block' element={<Block />} />
<Route path='/:currency/:network/tx/:tx' element={<TransactionHash />} />
Expand Down
6 changes: 6 additions & 0 deletions packages/insight/src/assets/images/block-group-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/insight/src/assets/images/block-group-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions packages/insight/src/components/block-sample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import nProgress from 'nprogress';
import React, {FC, useEffect, useState} from 'react';
import {useNavigate} from 'react-router-dom';
import {fetcher} from 'src/api/api';
import Info from 'src/components/info';
import {getApiRoot} from 'src/utilities/helper-methods';
import {BlocksType} from 'src/utilities/models';
import styled from 'styled-components';

const BlockChip = styled.div`
border: 4px solid ${({theme: {dark}}) => dark ? '#333' : '#ddd'};
border-radius: 10px;
padding: 1rem;
width: 12rem;
max-width: 400px;
background-color: transparent;
cursor: pointer;
text-align: center;
`;

const BlockSample: FC<{currency: string; network: string}> = ({currency, network}) => {
const navigate = useNavigate();

const [blocksList, setBlocksList] = useState<BlocksType[]>();
const [error, setError] = useState('');

useEffect(() => {
nProgress.start();
Promise.all([fetcher(`${getApiRoot(currency)}/${currency}/${network}/block?limit=5`)])
.then(([data]) => {
setBlocksList(data);
})
.catch((e: any) => {
setError(e.message || 'Something went wrong. Please try again later.');
})
.finally(() => {
nProgress.done();
});
}, []);

const gotoSingleBlockDetailsView = async (hash: string) => {
await navigate(`/${currency}/${network}/block/${hash}`);
};

if (error) return <Info type={'error'} message={error} />;
if (!blocksList?.length) return null;
return (
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
{blocksList.map((block: BlocksType, index: number) => {
const {height, hash, transactionCount, time, size} = block;
const milisecondsWhenMined = Date.now() - new Date(time).getTime();
const minutesWhenMined = Math.floor(milisecondsWhenMined / 60000);
return (
<React.Fragment key={index}>
<BlockChip onClick={() => gotoSingleBlockDetailsView(hash)}>
<b>
<div>{height}</div>
<div>{transactionCount} transactions</div>
<div>{size} bytes</div>
<div style={{whiteSpace: 'nowrap'}}>mined {minutesWhenMined} minutes ago</div>
</b>
</BlockChip>
{index !== blocksList.length - 1 && (
<div style={{fontSize: '1.5rem', color: '#666'}}>|</div>
)}
</React.Fragment>
);
})}
</div>
);
};

export default BlockSample;
83 changes: 83 additions & 0 deletions packages/insight/src/components/chain-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {FC, useEffect, useRef} from 'react';
import {useApi} from 'src/api/api';
import {Chart as ChartJS} from 'chart.js';
import {colorCodes} from 'src/utilities/constants';

const ChainHeader: FC<{currency: string; network: string}> = ({currency, network}) => {
const {data: priceDetails} = useApi(`https://bitpay.com/rates/${currency}/usd`);
const {data: priceDisplay} = useApi(
`https://bitpay.com/currencies/prices?currencyPairs=["${currency}:USD"]`,
);

const chartRef = useRef<HTMLCanvasElement | null>(null);
const chartInstanceRef = useRef<ChartJS | null>(null);

const price = network === 'mainnet' ? priceDetails?.data?.rate : 0;
const priceList = priceDisplay?.data?.[0]?.priceDisplay || [];

const chartData = {
labels: priceList,
datasets: [
{
data: priceList,
fill: false,
spanGaps: true,
borderColor: colorCodes[currency],
borderWidth: 2,
pointRadius: 0,
},
],
};

const options = {
scales: {
x: {display: false},
y: {display: false},
},
plugins: {legend: {display: false}},
events: [], // disable default events
responsive: true,
maintainAspectRatio: false,
tension: 0.5,
};

useEffect(() => {
if (chartRef.current) {
if (chartInstanceRef.current) {
chartInstanceRef.current.destroy();
}

chartInstanceRef.current = new ChartJS(chartRef.current, {
type: 'line',
data: chartData,
options,
});
}

return () => {
chartInstanceRef.current?.destroy();
};
}, [chartData, options]);

return (
<div style={{borderBottom: '1px solid', padding: '0 5px', height: 'fit-content', marginBottom: '0.5rem'}}>
<div style={{display: 'flex'}}>
<img
src={`https://bitpay.com/img/icon/currencies/${currency}.svg`}
alt={currency}
style={{height: '100px'}}
/>
{priceList.length > 0 && (
<div style={{height: '100px', width: '100%', minWidth: 0}}>
<canvas ref={chartRef} aria-label='price line chart' role='img' />
</div>
)}
</div>
<div style={{display: 'flex', justifyContent: 'space-around'}}>
<span style={{margin: '0 10px'}}>{price} USD </span>
</div>
</div>
);
};

export default ChainHeader;
6 changes: 3 additions & 3 deletions packages/insight/src/components/currency-tile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,12 @@ const CurrencyTile: FC<CurrencyTileProps> = ({currency}) => {
const {height, time, transactionCount, size} = data[0];
const imgSrc = `https://bitpay.com/img/icon/currencies/${currency}.svg`;

const gotoAllBlocks = async () => {
await navigate(`/${currency}/mainnet/blocks`);
const gotoChain = async () => {
await navigate(`/${currency}/mainnet/chain`);
};

return (
<CurrencyTileDiv currency={currency} onClick={gotoAllBlocks} key={currency}>
<CurrencyTileDiv currency={currency} onClick={gotoChain} key={currency}>
<CurrencyTileHeader>
<img src={imgSrc} width={35} height={35} alt={`${currency} logo`} />
<div>
Expand Down
99 changes: 99 additions & 0 deletions packages/insight/src/pages/chain.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import BlockSample from 'src/components/block-sample';
import React, {useEffect, useState} from 'react';
import ChainHeader from '../components/chain-header';
import {useNavigate, useParams} from 'react-router-dom';
import {useDispatch} from 'react-redux';
import {changeCurrency, changeNetwork} from 'src/store/app.actions';
import {getApiRoot, normalizeParams} from 'src/utilities/helper-methods';
import styled, { useTheme } from 'styled-components';
import {colorCodes, size} from 'src/utilities/constants';
import {fetcher} from 'src/api/api';
import BlockGroupDarkSvg from '../assets/images/block-group-dark.svg'
import BlockGroupLightSvg from '../assets/images/block-group-light.svg'
import nProgress from 'nprogress';

const HeaderDataContainer = styled.div`
width: 100%;
gap: 1rem;
display: flex;
flex-direction: column;
align-items: center; /* center on mobile by default */

@media screen and (min-width: ${size.mobileL}) {
flex-direction: row;
align-items: flex-start;
}
`;

const BlocksLinkChip = styled.div`
display: flex;
border-radius: 10px;
font: menu;
width: 100%;
gap: 0.5rem;
padding: 0.2rem 0;
margin: 0.25rem 0;
justify-content: center;
`

const Chain: React.FC = () => {
let {currency, network} = useParams<{currency: string; network: string}>();
const dispatch = useDispatch();
const navigate = useNavigate();
const [tipFee, setTipFee] = useState<number>();
const theme = useTheme();

useEffect(() => {
if (!currency || !network) return;
nProgress.start();
const _normalizeParams = normalizeParams(currency, network);
currency = _normalizeParams.currency;
network = _normalizeParams.network;

dispatch(changeCurrency(currency));
dispatch(changeNetwork(network));

Promise.all([
fetcher(`${getApiRoot(currency)}/${currency}/${network}/block/tip/fee`)
])
.then(([fee]) => {
setTipFee(fee.mean.toFixed(5));
nProgress.done();
});
}, [currency, network]);

const gotoBlocks = async () => {
await navigate(`/${currency}/${network}/blocks`);
};


const BlockGroupIcon: React.FC = () => {
return (
<img src={theme.dark ? BlockGroupLightSvg : BlockGroupDarkSvg}
style={{height:'1.5rem'}}/>
);
}

if (!currency || !network) return null;

return (
<>
<HeaderDataContainer>
<div style={{width: '100%', minWidth: 0}}>
<ChainHeader currency={currency} network={network}/>
<b>Tip Fee {tipFee} sats/byte</b>
</div>
<div style={{width: 'fit-content', display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
<BlocksLinkChip style={{backgroundColor: colorCodes[currency]}} onClick={gotoBlocks}>
<BlockGroupIcon />
<b>View all Blocks</b>
<BlockGroupIcon />
</BlocksLinkChip>
<BlockSample currency={currency} network={network}/>
</div>
</HeaderDataContainer>
</>
);
}

export default Chain;
3 changes: 0 additions & 3 deletions packages/insight/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {SUPPORTED_CURRENCIES} from '../utilities/constants';
import {SecondaryTitle} from '../assets/styles/titles';
import CurrencyTile from '../components/currency-tile';
import Masonry from 'react-masonry-css';
import {motion} from 'framer-motion';
Expand All @@ -24,8 +23,6 @@ const Home: React.FC = () => {

return (
<motion.div variants={routerFadeIn} animate='animate' initial='initial'>
<SecondaryTitle>Latest Blocks</SecondaryTitle>

<Masonry
breakpointCols={breakpointColumnsObj}
className='currency-masonry-grid'
Expand Down