From 4e1dc3662ec958a1afee996ab5dccad814dc7f29 Mon Sep 17 00:00:00 2001 From: Marina Terentii <125751323+Artentii@users.noreply.github.com> Date: Wed, 18 Oct 2023 13:58:45 +0200 Subject: [PATCH 01/40] Updated slot page --- .../client/pages/[network]/block/[id].tsx | 625 ++++++++++++++++++ packages/client/pages/[network]/slot/[id].tsx | 111 +--- 2 files changed, 661 insertions(+), 75 deletions(-) create mode 100644 packages/client/pages/[network]/block/[id].tsx diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx new file mode 100644 index 00000000..e609f4ee --- /dev/null +++ b/packages/client/pages/[network]/block/[id].tsx @@ -0,0 +1,625 @@ +import React, { useEffect, useState, useRef, useCallback, useContext } from 'react'; +import { useRouter } from 'next/router'; +import Head from 'next/head'; + +// Axios +import axiosClient from '../../../config/axios'; + +// Contexts +import ThemeModeContext from '../../../contexts/theme-mode/ThemeModeContext'; + +// Components +import Layout from '../../../components/layouts/Layout'; +import TabHeader from '../../../components/ui/TabHeader'; +import Loader from '../../../components/ui/Loader'; +import LinkValidator from '../../../components/ui/LinkValidator'; +import LinkSlot from '../../../components/ui/LinkSlot'; +import Arrow from '../../../components/ui/Arrow'; +import LinkEpoch from '../../../components/ui/LinkEpoch'; +import LinkEntity from '../../../components/ui/LinkEntity'; + +// Types +import { Block, Withdrawal } from '../../../types'; + +// Constants +import { ADDRESS_ZERO, ADDRESS_ZERO_SHORT } from '../../../constants'; + +type CardProps = { + title: string; + text?: string; + content?: React.ReactNode; +}; + +const Card = ({ title, text, content }: CardProps) => { + // Theme Mode Context + const { themeMode } = React.useContext(ThemeModeContext) ?? {}; + return ( + <> +
+

+ {title}: +

+
+ {text && ( +

+ {text} +

+ )} + + {content && <>{content}} +
+
+ + ); +}; + +const Slot = () => { + // Theme Mode Context + const { themeMode } = useContext(ThemeModeContext) ?? {}; + + // Next router + const router = useRouter(); + const { network, id } = router.query; + + // Refs + const slotRef = useRef(0); + const existsBlockRef = useRef(true); + const containerRef = useRef(null); + + // States + const [block, setBlock] = useState(null); + const [withdrawals, setWithdrawals] = useState>([]); + const [existsBlock, setExistsBlock] = useState(true); + const [countdownText, setCountdownText] = useState(''); + const [tabPageIndex, setTabPageIndex] = useState(0); + const [loadingBlock, setLoadingBlock] = useState(true); + const [loadingWithdrawals, setLoadingWithdrawals] = useState(true); + const [desktopView, setDesktopView] = useState(true); + const [blockGenesis, setBlockGenesis] = useState(0); + + // UseEffect + useEffect(() => { + if (id) { + slotRef.current = Number(id); + } + + if (network && ((id && !block) || (block && block.f_slot !== Number(id)))) { + getBlock(); + getWithdrawals(); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [network, id]); + useEffect(() => { + setDesktopView(window !== undefined && window.innerWidth > 768); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const shuffle = useCallback(() => { + const text: string = getCountdownText(); + setCountdownText(text); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const intervalID = setInterval(shuffle, 1000); + return () => clearInterval(intervalID); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [shuffle, slotRef.current]); + + // Get blocks + const getBlock = async () => { + try { + setLoadingBlock(true); + + const [response, genesisBlock] = await Promise.all([ + axiosClient.get(`/api/slots/${id}`, { + params: { + network, + }, + }), + axiosClient.get(`/api/networks/block/genesis`, { + params: { + network, + }, + }), + ]); + + const blockResponse: Block = response.data.block; + setBlock(blockResponse); + setBlockGenesis(genesisBlock.data.block_genesis); + + if (!blockResponse) { + const expectedTimestamp = (genesisBlock.data.block_genesis + Number(id) * 12000) / 1000; + + setBlock({ + f_epoch: Math.floor(Number(id) / 32), + f_slot: Number(id), + f_timestamp: expectedTimestamp, + }); + + setExistsBlock(false); + existsBlockRef.current = false; + + const timeDifference = new Date(expectedTimestamp * 1000).getTime() - new Date().getTime(); + + if (timeDifference > 0) { + setTimeout(() => { + if (Number(id) === slotRef.current) { + getBlock(); + } + }, timeDifference + 2000); + } else if (timeDifference > -10000) { + setTimeout(() => { + if (Number(id) === slotRef.current) { + getBlock(); + } + }, 1000); + } + } else { + setExistsBlock(true); + existsBlockRef.current = true; + } + } catch (error) { + console.log(error); + } finally { + setLoadingBlock(false); + } + }; + + // Get withdrawals + const getWithdrawals = async () => { + try { + setLoadingWithdrawals(true); + + const response = await axiosClient.get(`/api/slots/${id}/withdrawals`, { + params: { + network, + }, + }); + + setWithdrawals(response.data.withdrawals); + } catch (error) { + console.log(error); + } finally { + setLoadingWithdrawals(false); + } + }; + + // Get Short Address + const getShortAddress = (address: string | undefined) => { + return address && `${address.slice(0, 6)}...${address.slice(address.length - 6, address.length)}`; + }; + + const getTimeBlock = () => { + let text; + + if (block) { + if (block.f_timestamp) { + text = new Date(block.f_timestamp * 1000).toLocaleString('ja-JP'); + } else { + text = new Date(blockGenesis + Number(id) * 12000).toLocaleString('ja-JP'); + } + } + + return text + countdownText; + }; + + const getCountdownText = () => { + let text = ''; + + if (!existsBlockRef.current) { + const expectedTimestamp = (blockGenesis + slotRef.current * 12000) / 1000; + const timeDifference = new Date(expectedTimestamp * 1000).getTime() - new Date().getTime(); + + const minutesMiliseconds = 1000 * 60; + const hoursMiliseconds = minutesMiliseconds * 60; + const daysMiliseconds = hoursMiliseconds * 24; + const yearsMiliseconds = daysMiliseconds * 365; + + if (timeDifference > yearsMiliseconds) { + const years = Math.floor(timeDifference / yearsMiliseconds); + text = ` (in ${years} ${years > 1 ? 'years' : 'year'})`; + } else if (timeDifference > daysMiliseconds) { + const days = Math.floor(timeDifference / daysMiliseconds); + text = ` (in ${days} ${days > 1 ? 'days' : 'day'})`; + } else if (timeDifference > hoursMiliseconds) { + const hours = Math.floor(timeDifference / hoursMiliseconds); + text = ` (in ${hours} ${hours > 1 ? 'hours' : 'hour'})`; + } else if (timeDifference > minutesMiliseconds) { + const minutes = Math.floor(timeDifference / minutesMiliseconds); + text = ` (in ${minutes} ${minutes > 1 ? 'minutes' : 'minute'})`; + } else if (timeDifference > 1000) { + const seconds = Math.floor(timeDifference / 1000); + text = ` (in ${seconds} ${seconds > 1 ? 'seconds' : 'second'})`; + } else if (timeDifference < -10000) { + text = ' (data not saved)'; + } else { + text = ' (updating...)'; + } + } + + return text; + }; + + const handleMouseMove = (e: any) => { + if (containerRef.current) { + const x = e.pageX; + const limit = 0.15; + + if (x < containerRef.current.clientWidth * limit) { + containerRef.current.scrollLeft -= 10; + } else if (x > containerRef.current.clientWidth * (1 - limit)) { + containerRef.current.scrollLeft += 10; + } + } + }; + + //Tabs + const getSelectedTab = () => { + switch (tabPageIndex) { + case 0: + return getConsensusLayerView(); + + case 1: + return getExecutionLayerView(); + + case 2: + return desktopView ? getWithdrawlsViewDesktop() : getWithdrawlsViewMobile(); + } + }; + + //Tab sections information + const getInformationView = () => { + return ( +
+
+ setTabPageIndex(0)} + /> + {existsBlock && ( + <> + setTabPageIndex(1)} + /> + setTabPageIndex(2)} + /> + + )} +
+ + {getSelectedTab()} +
+ ); + }; + + //Overview tab view + const getConsensusLayerView = () => { + return ( +
+
+ } /> + + + {existsBlock && } />} + + {existsBlock && ( + + Proposed + + ) : ( + + Missed + + ) + } + /> + )} + + + + {existsBlock && ( + } /> + )} + + {existsBlock && } + + {existsBlock && ( + + )} + + {existsBlock && ( + + )} + + {existsBlock && ( + + )} + + {existsBlock && ( + + )} + + {existsBlock && ( + + )} + + {existsBlock && ( + + )} +
+
+ ); + }; + + const getExecutionLayerView = () => { + return ( +
+ + + + + + + + + +
+ ); + }; + + //View withdrawals table desktop + const getWithdrawlsViewDesktop = () => { + return ( +
+
+

Validator

+

Address

+

Amount

+
+ + {loadingWithdrawals ? ( +
+ +
+ ) : ( +
+ {withdrawals.map(element => ( +
+
+ +
+ +
+

{getShortAddress(element?.f_address)}

+
+ +

{(element.f_amount / 10 ** 9).toLocaleString()} ETH

+
+ ))} + + {withdrawals.length == 0 && ( +
+

No withdrawals

+
+ )} +
+ )} +
+ ); + }; + + //View withdrawals table mobile + const getWithdrawlsViewMobile = () => { + return ( +
+ {loadingWithdrawals ? ( +
+ +
+ ) : ( +
+ {withdrawals.map(element => ( +
+
+

+ Validator +

+ +
+ +
+

+ Address +

+

{getShortAddress(element?.f_address)}

+
+ +
+

+ Amount +

+

{(element.f_amount / 10 ** 9).toLocaleString()} ETH

+
+
+ ))} + + {withdrawals.length == 0 && ( +
+

No withdrawals

+
+ )} +
+ )} +
+ ); + }; + + //Overview slot page + return ( + + + + + + {/* Header */} +
+ + + + +

+ Slot {Number(id)?.toLocaleString()} +

+ + + + +
+ + {loadingBlock && ( +
+ +
+ )} + + {block && !loadingBlock && getInformationView()} +
+ ); +}; + +export default Slot; diff --git a/packages/client/pages/[network]/slot/[id].tsx b/packages/client/pages/[network]/slot/[id].tsx index 89420382..fa283b57 100644 --- a/packages/client/pages/[network]/slot/[id].tsx +++ b/packages/client/pages/[network]/slot/[id].tsx @@ -21,16 +21,13 @@ import LinkEntity from '../../../components/ui/LinkEntity'; // Types import { Block, Withdrawal } from '../../../types'; -// Constants -import { ADDRESS_ZERO, ADDRESS_ZERO_SHORT } from '../../../constants'; -import EpochAnimation from '../../../components/layouts/EpochAnimation'; - type CardProps = { title: string; text?: string; content?: React.ReactNode; }; +//Card style const Card = ({ title, text, content }: CardProps) => { // Theme Mode Context const { themeMode } = React.useContext(ThemeModeContext) ?? {}; @@ -205,6 +202,7 @@ const Slot = () => { return address && `${address.slice(0, 6)}...${address.slice(address.length - 6, address.length)}`; }; + // Get Time Block const getTimeBlock = () => { let text; @@ -219,6 +217,7 @@ const Slot = () => { return text + countdownText; }; + // Get Countdown Text const getCountdownText = () => { let text = ''; @@ -256,6 +255,7 @@ const Slot = () => { return text; }; + // Get Handle Mouse const handleMouseMove = (e: any) => { if (containerRef.current) { const x = e.pageX; @@ -269,53 +269,42 @@ const Slot = () => { } }; - //Tabs + //TABLE + + //TABS const getSelectedTab = () => { switch (tabPageIndex) { case 0: - return getConsensusLayerView(); + return getOverview(); case 1: - return getExecutionLayerView(); - - case 2: - return desktopView ? getWithdrawlsViewDesktop() : getWithdrawlsViewMobile(); + return desktopView ? getWithdrawlsDesktop() : getWithdrawlsMobile(); } }; - //Tab sections information + //TABS - Overview & withdrawals const getInformationView = () => { return (
- setTabPageIndex(0)} - /> + setTabPageIndex(0)} /> {existsBlock && ( <> setTabPageIndex(1)} /> - setTabPageIndex(2)} - /> )}
- {getSelectedTab()}
); }; - //Overview tab view - const getConsensusLayerView = () => { + //Overview tab - table + const getOverview = () => { return (
{ color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} > + {/* Table */}
} /> + } /> {existsBlock && ( @@ -410,48 +401,8 @@ const Slot = () => { ); }; - const getExecutionLayerView = () => { - return ( -
- - - - - - - - - -
- ); - }; - - //View withdrawals table desktop - const getWithdrawlsViewDesktop = () => { + //Withdrawals tab - table desktop + const getWithdrawlsDesktop = () => { return (
{ ); }; - //View withdrawals table mobile - const getWithdrawlsViewMobile = () => { + //Withdrawals tab - table mobile + const getWithdrawlsMobile = () => { return (
{
{withdrawals.map(element => (
{
))} - {withdrawals.length == 0 && ( -
-

No withdrawals

+
+

No withdrawals

)}
@@ -590,7 +551,7 @@ const Slot = () => { ); }; - //Overview slot page + //OVERVIEW SLOT PAGE return ( From cbea7d764dd298e7237b1a20b2b86b722f87a0f7 Mon Sep 17 00:00:00 2001 From: Marina Terentii <125751323+Artentii@users.noreply.github.com> Date: Wed, 18 Oct 2023 17:18:24 +0200 Subject: [PATCH 02/40] Added new block page --- .../client/pages/[network]/block/[id].tsx | 422 +++++++++++------- packages/client/pages/[network]/slot/[id].tsx | 12 +- .../public/static/images/icons/copy_dark.webp | Bin 0 -> 504 bytes .../static/images/icons/copy_light.webp | Bin 0 -> 516 bytes .../public/static/images/icons/send_dark.webp | Bin 0 -> 980 bytes .../static/images/icons/send_light.webp | Bin 0 -> 934 bytes 6 files changed, 264 insertions(+), 170 deletions(-) create mode 100644 packages/client/public/static/images/icons/copy_dark.webp create mode 100644 packages/client/public/static/images/icons/copy_light.webp create mode 100644 packages/client/public/static/images/icons/send_dark.webp create mode 100644 packages/client/public/static/images/icons/send_light.webp diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index e609f4ee..e21baccf 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -17,19 +17,21 @@ import LinkSlot from '../../../components/ui/LinkSlot'; import Arrow from '../../../components/ui/Arrow'; import LinkEpoch from '../../../components/ui/LinkEpoch'; import LinkEntity from '../../../components/ui/LinkEntity'; +import CustomImage from '../../../components/ui/CustomImage'; +import TooltipContainer from '../../../components/ui/TooltipContainer'; +import TooltipResponsive from '../../../components/ui/TooltipResponsive'; +import ValidatorStatus from '../../../components/ui/ValidatorStatus'; // Types import { Block, Withdrawal } from '../../../types'; -// Constants -import { ADDRESS_ZERO, ADDRESS_ZERO_SHORT } from '../../../constants'; - type CardProps = { title: string; text?: string; content?: React.ReactNode; }; +//Card style const Card = ({ title, text, content }: CardProps) => { // Theme Mode Context const { themeMode } = React.useContext(ThemeModeContext) ?? {}; @@ -63,7 +65,7 @@ const Card = ({ title, text, content }: CardProps) => { ); }; -const Slot = () => { +const BlockPage = () => { // Theme Mode Context const { themeMode } = useContext(ThemeModeContext) ?? {}; @@ -204,6 +206,7 @@ const Slot = () => { return address && `${address.slice(0, 6)}...${address.slice(address.length - 6, address.length)}`; }; + // Get Time Block const getTimeBlock = () => { let text; @@ -218,6 +221,7 @@ const Slot = () => { return text + countdownText; }; + // Get Countdown Text const getCountdownText = () => { let text = ''; @@ -255,6 +259,7 @@ const Slot = () => { return text; }; + // Get Handle Mouse const handleMouseMove = (e: any) => { if (containerRef.current) { const x = e.pageX; @@ -268,53 +273,42 @@ const Slot = () => { } }; - //Tabs + //TABLE + + //TABS const getSelectedTab = () => { switch (tabPageIndex) { case 0: - return getConsensusLayerView(); + return getOverview(); case 1: - return getExecutionLayerView(); - - case 2: - return desktopView ? getWithdrawlsViewDesktop() : getWithdrawlsViewMobile(); + return desktopView ? getTransactionsDesktop() : getTransactionsMobile(); } }; - //Tab sections information + //TABS - Overview & withdrawals const getInformationView = () => { return ( -
+
- setTabPageIndex(0)} - /> + setTabPageIndex(0)} /> {existsBlock && ( <> setTabPageIndex(1)} /> - setTabPageIndex(2)} - /> )}
- {getSelectedTab()}
); }; - //Overview tab view - const getConsensusLayerView = () => { + //Overview tab - table + const getOverview = () => { return (
{ color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} > + {/* Table */}
- } /> + - - {existsBlock && } />} - - {existsBlock && ( - - Proposed - - ) : ( - - Missed - - ) - } - /> - )} - - - {existsBlock && ( - } /> - )} - - {existsBlock && } - - {existsBlock && ( - - )} - - {existsBlock && ( - - )} - - {existsBlock && ( - - )} - - {existsBlock && ( - - )} - - {existsBlock && ( - - )} - - {existsBlock && ( - - )} + + + + + + +
); }; - const getExecutionLayerView = () => { - return ( -
- - - - - - - - - -
- ); - }; - - //View withdrawals table desktop - const getWithdrawlsViewDesktop = () => { + //Withdrawals tab - table desktop + const getTransactionsDesktop = () => { return (
-

Validator

-

Address

-

Amount

+
+

Txn Hash

+ + + + + Time at which the slot + should have passed + (calculated since genesis) + + } + top='34px' + polygonLeft + /> + +
+
+

Method

+ + + + + Time at which the slot + should have passed + (calculated since genesis) + + } + top='34px' + polygonLeft + /> + +
+
+

Age

+ + + + + Time at which the slot + should have passed + (calculated since genesis) + + } + top='34px' + polygonLeft + /> + +
+

From

+

To

+
+

Value

+ + + + + Time at which the slot + should have passed + (calculated since genesis) + + } + top='34px' + polygonLeft + /> + +
+
+

Txn Fee

+ + + + + Time at which the slot + should have passed + (calculated since genesis) + + } + top='34px' + polygonLeft + /> + +
{loadingWithdrawals ? ( @@ -469,7 +489,7 @@ const Slot = () => {
) : (
{ key={element.f_val_idx} >
- +

{getShortAddress(element?.f_address)}

-

{getShortAddress(element?.f_address)}

+
+

{'9hrs 51mins ago'}

+ +

{getShortAddress(element?.f_address)}

+ +

{getShortAddress(element?.f_address)}

+

{(element.f_amount / 10 ** 9).toLocaleString()} ETH

+

{(element.f_amount / 10 ** 9).toLocaleString()}

))} @@ -504,12 +536,12 @@ const Slot = () => { ); }; - //View withdrawals table mobile - const getWithdrawlsViewMobile = () => { + //Withdrawals tab - table mobile + const getTransactionsMobile = () => { return (
{
{withdrawals.map(element => (
{ color: themeMode?.darkMode ? 'var(--white)' : 'var(--darkGray)', }} > - Validator + Txn Hash +

+

{getShortAddress(element?.f_address)}

+
+
+

+ Method

-

{ color: themeMode?.darkMode ? 'var(--white)' : 'var(--darkGray)', }} > - Address + Age

-

{getShortAddress(element?.f_address)}

+

{'9hrs 51mins ago'}

+
+
+

+ From +

+

+ To +

+
+
+
+

{getShortAddress(element?.f_address)}

+
+ +
+

{getShortAddress(element?.f_address)}

+
-

{ color: themeMode?.darkMode ? 'var(--white)' : 'var(--darkGray)', }} > - Amount + Value

{(element.f_amount / 10 ** 9).toLocaleString()} ETH

+
+

+ Txn Fee +

+

{(element.f_amount / 10 ** 9).toLocaleString()}

+
))} - {withdrawals.length == 0 && ( -
-

No withdrawals

+
+

No withdrawals

)}
@@ -584,7 +678,7 @@ const Slot = () => { ); }; - //Overview slot page + //OVERVIEW SLOT PAGE return ( @@ -603,7 +697,7 @@ const Slot = () => { color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} > - Slot {Number(id)?.toLocaleString()} + Block {Number(id)?.toLocaleString()} @@ -622,4 +716,4 @@ const Slot = () => { ); }; -export default Slot; +export default BlockPage; diff --git a/packages/client/pages/[network]/slot/[id].tsx b/packages/client/pages/[network]/slot/[id].tsx index fa283b57..6012353b 100644 --- a/packages/client/pages/[network]/slot/[id].tsx +++ b/packages/client/pages/[network]/slot/[id].tsx @@ -278,7 +278,7 @@ const Slot = () => { return getOverview(); case 1: - return desktopView ? getWithdrawlsDesktop() : getWithdrawlsMobile(); + return desktopView ? getWithdrawalsDesktop() : getWithdrawalsMobile(); } }; @@ -291,7 +291,7 @@ const Slot = () => { {existsBlock && ( <> setTabPageIndex(1)} /> @@ -317,7 +317,7 @@ const Slot = () => { {/* Table */}
} /> - } /> + } /> {existsBlock && ( @@ -353,7 +353,7 @@ const Slot = () => { {existsBlock && ( - } /> + } /> )} {existsBlock && } @@ -402,7 +402,7 @@ const Slot = () => { }; //Withdrawals tab - table desktop - const getWithdrawlsDesktop = () => { + const getWithdrawalsDesktop = () => { return (
{ }; //Withdrawals tab - table mobile - const getWithdrawlsMobile = () => { + const getWithdrawalsMobile = () => { return (
89b0b?o6{-zybh*h@V6Wx=~MqP8@WP2fJhJ+ z5`cOJ03LUE(LMlTB-?G9uS6>6ED#4!ss9!&5dVX2Afo>c+(uHQRF>hWZuSqNX0&!_ zaYHFe_+W`IGCJtn2e2Fk$)+xJ0!&0iqf7@!u`qA!w&2A?9qo?cYeei$U}42>Xnbpb z`vnKhAJ}Xh9`>CaqV_YG4AsXg9nR@tXK|D4v3del&lvK?N7|>`WsycPG-|gywQs5@ znV=Z2inYiJcu79`!O{8Ze1=Z#btcKptUbEIQVd9{vE4v4(b2>N-eRJwg%4CVG8(wT zQ^&&~r;dYx&i(i9bE*KMGli98?Sm0e_XVcavnW1(MK2TSRZINEqpGjo!tu*JiddXuk3X4zz<9pdxSq;>@Gu%zbvD($HSQ6=bnjdM literal 0 HcmV?d00001 diff --git a/packages/client/public/static/images/icons/copy_light.webp b/packages/client/public/static/images/icons/copy_light.webp new file mode 100644 index 0000000000000000000000000000000000000000..0484e390b4a8837d94d375936349e718b3d7b8ba GIT binary patch literal 516 zcmV+f0{i_^Nk&He0RRA3MM6+kP&iEQ0RR9mF+dCuC&8d?BuBn}_o$vfr8YDOByD8L z-aph)+_r6`+!;61_gN!kBT2IaGYXIL{xWay0o=RT@CFDkr~%y2FhgsAPgpqcQK4a0 z5E>>d5K#~g3WBOQAA0cMU8T?81;j|U+csbTl`};p@D%$m(gg7z=msMC@4#&&MM`Dq z7;1a};LP|#z)GlHR*DWKeU@c_sl5ZO|F~qfnJ$2ZbIu~q06V8}4!RZePGbAiZQv^6 zbbrvQ=zc=~(m#I2o$?o!eb=+!D6`V~8zhTW*Bc#9*~46MAKjw5jr#X4(w6tQPxtN= z$M6%(ZjWjoR&SRGhR>Vf*0~ngxOD70{qYw11f$y9ETR`vdvqHbCg5T3Pa0yvu^v@Yoscwn7&0^AJ<-C_HeM|3*mpthc? zjg+mi6Kur1nr)n;L~fo*j{m?7W)sr@#kAC6sMLhK{V=z}b3)p99=+ToR1pl^p7FKn zn~RUSwhi~AAD>ZmE{!=I&ahEQw`Q2oM+p@6N8jbq3PWx|=7X1jSAL&< zk+-x$mjZB|NiR{Xu0(|tg?o3{CUZKCKF(8X>=`}%_9M6ac-Ock_oTVyo7Fa%)$b+V G;s5|%?)Y8+ literal 0 HcmV?d00001 diff --git a/packages/client/public/static/images/icons/send_dark.webp b/packages/client/public/static/images/icons/send_dark.webp new file mode 100644 index 0000000000000000000000000000000000000000..c8952d9a81ec4796c949864337b367048e62f1ef GIT binary patch literal 980 zcmV;_11tPeNk&G@0{{S5MM6+kP&iD#0{{RoR=^byw}G&cWXwPM)qnVF13}xi@wWe1 z&mTa?xNX}&bc*gjVy8IVD=?!-lEQQpFmXT}L=ygApn!n;-(1sMwcmqud$1dGBPKCO zS%*b3G)TfgQ<}gSROMQ`p;{=Y?V}6^4HbxJR8SH@`*mNwdGMW|qcT_au+p=p{{;yG zfVORKwr?Fa^#}FodRFP(fQe9wWXuI?e`XYg((>)>?JOo zBJ;gvo0Z`+Shz&w%%g_yXQh$Oj6)ZSlS@R%+Kv=2M=eoKQ2FmJ-I6w&S|7EET z1F*urkOoW=hVK0hUGM6S;WAmVQZ^f4TYg;lmZtXuTjew z7CSJa6tmD5zs|s8h9mzyqpD9A+*C8z!k5No56p}4% zdJp%pt{{~e<$QORfDcP&O1U;DC>W5-V9$W940KOMrzvPCQ*>eC*h5W7%iucR$P-Af1_C_CKyin8VX_bP)ZZ@1I5VfhViO^u<1r$oKY2}nmkf*PvJ(!F@>c7Z*ho}9Vcv?CMH3utHO(nI3hYKa z7YP)jS>X(71fxYFf&bW`rj;%>tgQ3%-UnZJutpWhX C4BpQG literal 0 HcmV?d00001 diff --git a/packages/client/public/static/images/icons/send_light.webp b/packages/client/public/static/images/icons/send_light.webp new file mode 100644 index 0000000000000000000000000000000000000000..708f57d27b24f49cf1c0637dfb6092bb0048fad2 GIT binary patch literal 934 zcmV;X16ll1Nk&GV0{{S5MM6+kP&iDH0{{RoR=^byN5P&AUzq<8HSZhXY4gu4lsSQfS6`JTgc6zS+?GO8NKgQQh4WnzMt>4cohPmr4e`{9^)e~Fup%;~i z-HRWDsC=_r>AWZjJiC~ze99Adi^m1AN@2Yod=CiD4Bs&_dWC{#DxU~BDkRPfi^XWP zaWO(ZQDM3Pdx<7{r@Qr6@cc#Y`H-^vs4OZ67Q(iPn{>mq=3#oY;O-tc{S!DJA;L*k?)L>iyU_VSe2L{=n1{F@1bgP^O8#B|Z4Sa? z_>T1778z6ft;Mr}hL4u5?IBfmuGd*^|qZ!(RbyD_P;?Fqy9>exn=?=l%>w zwkik9HtS?WBfoVLIw$0Rp$Q5v_CGZgl;SDb-_{AJb8>VNvmv1ufF5~h1<;y@ycLu6 z5;Br5{xzn-Qkg}==tMxA7!ldHDcpuz7Lj>_AzWwY93nLI4`MsY5!HwDhPK0GmXw&c zC0DG9k(xSs8+I&3R7YR>3l~z?i~#cAPipTi;^!G6%2M06;9UM5B0DaI8ez_Pu>es$ z_Y8a7U_Y8sDm82W7fWKw67b;T$R5=hxJDnboS+=t#8Q~L6g@=az@Zea Date: Wed, 18 Oct 2023 17:34:51 +0200 Subject: [PATCH 03/40] Update Menu.tsx --- packages/client/components/ui/Menu.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/client/components/ui/Menu.tsx b/packages/client/components/ui/Menu.tsx index d033d671..96c62c6d 100644 --- a/packages/client/components/ui/Menu.tsx +++ b/packages/client/components/ui/Menu.tsx @@ -56,6 +56,10 @@ const Menu = () => { name: 'Validators', route: '/validators', }, + { + name: 'Blocks', + route: '/blocks', + }, ], Networks: networks.length > 0 From 7a86d3ee38fb6e2769824d608958c3cb03f6b9d6 Mon Sep 17 00:00:00 2001 From: Josep Chetrit Date: Mon, 30 Oct 2023 22:05:03 +0100 Subject: [PATCH 04/40] implement queries for block page --- packages/client/components/ui/LinkBlock.tsx | 43 +++++++++++++++++++ .../client/pages/[network]/block/[id].tsx | 30 ++++++------- packages/client/pages/[network]/slot/[id].tsx | 3 +- packages/client/types/index.ts | 14 ++++++ packages/server/controllers/blocks.ts | 38 ++++++++++++++++ packages/server/controllers/slots.ts | 3 +- packages/server/models/server.ts | 3 ++ packages/server/routes/blocks.ts | 22 ++++++++++ 8 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 packages/client/components/ui/LinkBlock.tsx create mode 100644 packages/server/controllers/blocks.ts create mode 100644 packages/server/routes/blocks.ts diff --git a/packages/client/components/ui/LinkBlock.tsx b/packages/client/components/ui/LinkBlock.tsx new file mode 100644 index 00000000..7d48cc6a --- /dev/null +++ b/packages/client/components/ui/LinkBlock.tsx @@ -0,0 +1,43 @@ +import React, { useContext } from 'react'; + +// Contexts +import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; + +// Components +import LinkIcon from './LinkIcon'; +import NetworkLink from './NetworkLink'; + +// Types +type Props = { + block: number | undefined; + children?: React.ReactNode; + mxAuto?: boolean; +}; + +const LinkBlock = ({ block, children, mxAuto }: Props) => { + // Theme Mode Context + const { themeMode } = useContext(ThemeModeContext) ?? {}; + + const baseStyle = { + color: themeMode?.darkMode ? 'var(--purple)' : 'var(--darkPurple)', + }; + return ( + + {children ?? ( + <> +

{block?.toLocaleString()}

+ + + )} +
+ ); +}; + +export default LinkBlock; diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index e21baccf..07eaf2e0 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -23,7 +23,7 @@ import TooltipResponsive from '../../../components/ui/TooltipResponsive'; import ValidatorStatus from '../../../components/ui/ValidatorStatus'; // Types -import { Block, Withdrawal } from '../../../types'; +import { BlockEL, Withdrawal } from '../../../types'; type CardProps = { title: string; @@ -79,7 +79,7 @@ const BlockPage = () => { const containerRef = useRef(null); // States - const [block, setBlock] = useState(null); + const [block, setBlock] = useState(null); const [withdrawals, setWithdrawals] = useState>([]); const [existsBlock, setExistsBlock] = useState(true); const [countdownText, setCountdownText] = useState(''); @@ -128,7 +128,7 @@ const BlockPage = () => { setLoadingBlock(true); const [response, genesisBlock] = await Promise.all([ - axiosClient.get(`/api/slots/${id}`, { + axiosClient.get(`/api/blocks/${id}`, { params: { network, }, @@ -140,7 +140,7 @@ const BlockPage = () => { }), ]); - const blockResponse: Block = response.data.block; + const blockResponse: BlockEL = response.data.block; setBlock(blockResponse); setBlockGenesis(genesisBlock.data.block_genesis); @@ -295,7 +295,7 @@ const BlockPage = () => { {existsBlock && ( <> setTabPageIndex(1)} /> @@ -320,16 +320,16 @@ const BlockPage = () => { > {/* Table */}
- - + + } /> - - - + + + - - - + + +
); @@ -527,7 +527,7 @@ const BlockPage = () => { {withdrawals.length == 0 && (
-

No withdrawals

+

No transactions

)}
@@ -669,7 +669,7 @@ const BlockPage = () => { color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} > -

No withdrawals

+

No transactions

)}
diff --git a/packages/client/pages/[network]/slot/[id].tsx b/packages/client/pages/[network]/slot/[id].tsx index 6012353b..e4925536 100644 --- a/packages/client/pages/[network]/slot/[id].tsx +++ b/packages/client/pages/[network]/slot/[id].tsx @@ -20,6 +20,7 @@ import LinkEntity from '../../../components/ui/LinkEntity'; // Types import { Block, Withdrawal } from '../../../types'; +import LinkBlock from '../../../components/ui/LinkBlock'; type CardProps = { title: string; @@ -317,7 +318,7 @@ const Slot = () => { {/* Table */}
} /> - } /> + } /> {existsBlock && ( diff --git a/packages/client/types/index.ts b/packages/client/types/index.ts index 6997e4ea..bcba68dc 100644 --- a/packages/client/types/index.ts +++ b/packages/client/types/index.ts @@ -50,6 +50,20 @@ export type Block = { f_voluntary_exits?: number; }; +export type BlockEL = { + f_epoch: number; + f_slot: number; + f_timestamp: number; + f_el_block_number?: number; + f_el_block_hash?: string; + f_el_fee_recp?: string; + f_el_gas_limit?: number; + f_el_gas_used?: number; + f_el_transactions?: number; + f_payload_size_bytes?: number; +}; + + export type Slot = { f_proposer_slot: number; f_pool_name: string; diff --git a/packages/server/controllers/blocks.ts b/packages/server/controllers/blocks.ts new file mode 100644 index 00000000..205664e3 --- /dev/null +++ b/packages/server/controllers/blocks.ts @@ -0,0 +1,38 @@ +import { Request, Response } from 'express'; +import { pgPools, pgListeners } from '../config/db'; + +export const getBlockById = async (req: Request, res: Response) => { + + try { + + const { id } = req.params; + const { network } = req.query; + + const pgPool = pgPools[network as string]; + + const [ block ] = + await Promise.all([ + pgPool.query(` + SELECT f_timestamp, f_slot, f_epoch + f_el_fee_recp, f_el_gas_limit, f_el_gas_used, + f_el_transactions, f_el_block_hash, f_payload_size_bytes + FROM t_block_metrics + WHERE f_el_block_number = '${id}' + `) + ]); + // 18022299 + + + res.json({ + block: { + ...block.rows[0], + }, + }); + + } catch (error) { + console.log(error); + return res.status(500).json({ + msg: 'An error occurred on the server' + }); + } +}; \ No newline at end of file diff --git a/packages/server/controllers/slots.ts b/packages/server/controllers/slots.ts index ac4ef5d5..a5ce88e6 100644 --- a/packages/server/controllers/slots.ts +++ b/packages/server/controllers/slots.ts @@ -135,7 +135,8 @@ export const getSlotById = async (req: Request, res: Response) => { t_block_metrics.f_attestations, t_block_metrics.f_deposits, t_block_metrics.f_proposer_slashings, t_block_metrics.f_att_slashings, t_block_metrics.f_voluntary_exits, t_block_metrics.f_sync_bits, t_block_metrics.f_el_fee_recp, t_block_metrics.f_el_gas_limit, t_block_metrics.f_el_gas_used, - t_block_metrics.f_el_transactions, t_block_metrics.f_el_block_hash, t_eth2_pubkeys.f_pool_name + t_block_metrics.f_el_transactions, t_block_metrics.f_el_block_hash, t_eth2_pubkeys.f_pool_name, + t_block_metrics.f_el_block_number FROM t_block_metrics LEFT OUTER JOIN t_eth2_pubkeys ON t_block_metrics.f_proposer_index = t_eth2_pubkeys.f_val_idx WHERE f_slot = '${id}' diff --git a/packages/server/models/server.ts b/packages/server/models/server.ts index 8f10936f..2f89a6bd 100644 --- a/packages/server/models/server.ts +++ b/packages/server/models/server.ts @@ -5,6 +5,7 @@ import { dbConnection } from '../config/db'; import entitiesRoutes from '../routes/entities'; import epochsRoutes from '../routes/epochs'; import slotsRoutes from '../routes/slots'; +import blocksRoutes from '../routes/blocks'; import validatorsRoutes from '../routes/validators'; import networksRoutes from '../routes/networks'; @@ -17,6 +18,7 @@ class Server { entities: '/api/entities', epochs: '/api/epochs', slots: '/api/slots', + blocks: '/api/blocks', validators: '/api/validators', networks: '/api/networks' }; @@ -63,6 +65,7 @@ class Server { this.app.use(this.paths.entities, entitiesRoutes); this.app.use(this.paths.epochs, epochsRoutes); this.app.use(this.paths.slots, slotsRoutes); + this.app.use(this.paths.blocks, blocksRoutes); this.app.use(this.paths.validators, validatorsRoutes); this.app.use(this.paths.networks, networksRoutes); } diff --git a/packages/server/routes/blocks.ts b/packages/server/routes/blocks.ts new file mode 100644 index 00000000..dee382e2 --- /dev/null +++ b/packages/server/routes/blocks.ts @@ -0,0 +1,22 @@ +import { Router } from 'express'; +import { check, query } from 'express-validator'; + +import { + getBlockById, +} from '../controllers/blocks'; + +import { checkFields } from '../middlewares/check-fields'; +import { existsNetwork } from '../helpers/network-validator'; + +const router = Router(); + + +router.get('/:id', [ + check('id').isInt({ min: 0, max: 2147483647 }), + query('network').not().isEmpty(), + query('network').custom(existsNetwork), + checkFields, +], getBlockById); + + +export default router; \ No newline at end of file From e25ca05b1ddf9142a2cee6b3331bfa188d8ca81a Mon Sep 17 00:00:00 2001 From: Marina Terentii <125751323+Artentii@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:36:49 +0100 Subject: [PATCH 05/40] Update style new blocks page -Linked arrows -Fixed typo -Adjusted table size --- packages/client/components/ui/LinkSlot.tsx | 2 +- .../client/pages/[network]/block/[id].tsx | 35 +++++++++++-------- packages/client/types/index.ts | 1 - 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/client/components/ui/LinkSlot.tsx b/packages/client/components/ui/LinkSlot.tsx index 5b62d157..e51ebaa5 100644 --- a/packages/client/components/ui/LinkSlot.tsx +++ b/packages/client/components/ui/LinkSlot.tsx @@ -26,7 +26,7 @@ const LinkSlot = ({ slot, children, mxAuto }: Props) => { { // Get Short Address const getShortAddress = (address: string | undefined) => { - return address && `${address.slice(0, 6)}...${address.slice(address.length - 6, address.length)}`; + if (typeof address === 'string') { + return `${address.slice(0, 6)}...${address.slice(address.length - 6, address.length)}`; + } else { + return 'Invalid Address'; + } }; // Get Time Block @@ -289,8 +294,8 @@ const BlockPage = () => { //TABS - Overview & withdrawals const getInformationView = () => { return ( -
-
+
+
setTabPageIndex(0)} /> {existsBlock && ( <> @@ -311,7 +316,7 @@ const BlockPage = () => { const getOverview = () => { return (
{ > {/* Table */}
- + } /> - - + + @@ -335,12 +340,12 @@ const BlockPage = () => { ); }; - //Withdrawals tab - table desktop + //Transactions tab - table desktop const getTransactionsDesktop = () => { return (
{ ); }; - //Withdrawals tab - table mobile + //Transactions tab - table mobile const getTransactionsMobile = () => { return (
{ {/* Header */}
- + - +

{ Block {Number(id)?.toLocaleString()}

- + - +
{loadingBlock && ( diff --git a/packages/client/types/index.ts b/packages/client/types/index.ts index bcba68dc..8a71887a 100644 --- a/packages/client/types/index.ts +++ b/packages/client/types/index.ts @@ -63,7 +63,6 @@ export type BlockEL = { f_payload_size_bytes?: number; }; - export type Slot = { f_proposer_slot: number; f_pool_name: string; From 666fcea84318d43a5b49565313273932bbe82fe9 Mon Sep 17 00:00:00 2001 From: Marina Terentii <125751323+Artentii@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:43:01 +0100 Subject: [PATCH 06/40] Added new blocks page to the menu --- .../components/layouts/BlocksLayout.tsx | 245 ++++++++++++++++++ .../client/pages/[network]/block/[id].tsx | 4 +- packages/client/pages/[network]/blocks.tsx | 118 +++++++++ 3 files changed, 364 insertions(+), 3 deletions(-) create mode 100644 packages/client/components/layouts/BlocksLayout.tsx create mode 100644 packages/client/pages/[network]/blocks.tsx diff --git a/packages/client/components/layouts/BlocksLayout.tsx b/packages/client/components/layouts/BlocksLayout.tsx new file mode 100644 index 00000000..fe308558 --- /dev/null +++ b/packages/client/components/layouts/BlocksLayout.tsx @@ -0,0 +1,245 @@ +import React, { useState, useRef, useContext, useEffect } from 'react'; + +// Contexts +import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; + +// Components +import LinkValidator from '../ui/LinkValidator'; +import LinkSlot from '../ui/LinkSlot'; + +// Types +import { Slot } from '../../types'; + +import axiosClient from '../../config/axios'; +import { useRouter } from 'next/router'; + +// Props +type Props = { + slots: Slot[]; +}; + +const Blocks = ({ slots }: Props) => { + // Theme Mode Context + const { themeMode } = useContext(ThemeModeContext) ?? {}; + + // Router + const router = useRouter(); + const { network } = router.query; + + // Refs + const containerRef = useRef(null); + + // States + const [desktopView, setDesktopView] = useState(true); + const [blockGenesis, setBlockGenesis] = useState(0); + + useEffect(() => { + setDesktopView(window !== undefined && window.innerWidth > 768); + + if (network && blockGenesis == 0) { + getBlockGenesis(network as string); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleMouseMove = (e: any) => { + if (containerRef.current) { + const x = e.pageX; + const limit = 0.15; + + if (x < containerRef.current.clientWidth * limit) { + containerRef.current.scrollLeft -= 10; + } else if (x > containerRef.current.clientWidth * (1 - limit)) { + containerRef.current.scrollLeft += 10; + } + } + }; + + const getBlockGenesis = async (network: string) => { + try { + const genesisBlock = await axiosClient.get(`/api/networks/block/genesis`, { + params: { + network, + }, + }); + + setBlockGenesis(genesisBlock.data.block_genesis); + } catch (error) { + console.log(error); + } + }; + + //View blocks table desktop + const getContentBlocksDesktop = () => { + const titles = ['Block Number', 'Slot', 'Datetime', 'Transactions']; + return ( +
+
+
+ {titles.map((title, index) => ( +

+ {title} +

+ ))} +
+ + {slots.map(element => ( +
+
+ +
+ +
+ +
+ +

+ {new Date(blockGenesis + Number(element.f_proposer_slot) * 12000).toLocaleString( + 'ja-JP' + )} +

+ +

{(element.withdrawals / 10 ** 9).toLocaleString()}

+
+ ))} + + {slots.length === 0 && ( +
+

No blocks

+
+ )} +
+
+ ); + }; + //View blocks table mobile + const getContentBlocksMobile = () => { + return ( +
+ {slots.map(slot => ( +
+
+
+

+ Block number: +

+ +
+ +
+

+ Slot: +

+ +
+ +
+

+ Datetime: +

+
+

+ {new Date( + blockGenesis + Number(slot.f_proposer_slot) * 12000 + ).toLocaleDateString('ja-JP', { + year: 'numeric', + month: 'numeric', + day: 'numeric', + })} +

+

+ {new Date( + blockGenesis + Number(slot.f_proposer_slot) * 12000 + ).toLocaleTimeString('ja-JP', { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + })} +

+
+
+ +
+

+ Transactions: +

+

{(slot.withdrawals / 10 ** 9).toLocaleString()}

+
+
+
+ ))} + + {slots.length === 0 && ( +
+

No slots

+
+ )} +
+ ); + }; + + return
{desktopView ? getContentBlocksDesktop() : getContentBlocksMobile()}
; +}; + +export default Blocks; diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index 1d22adfe..89163a0a 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -279,7 +279,6 @@ const BlockPage = () => { }; //TABLE - //TABS const getSelectedTab = () => { switch (tabPageIndex) { @@ -290,7 +289,6 @@ const BlockPage = () => { return desktopView ? getTransactionsDesktop() : getTransactionsMobile(); } }; - //TABS - Overview & withdrawals const getInformationView = () => { return ( @@ -683,7 +681,7 @@ const BlockPage = () => { ); }; - //OVERVIEW SLOT PAGE + //OVERVIEW BLOCK PAGE return ( diff --git a/packages/client/pages/[network]/blocks.tsx b/packages/client/pages/[network]/blocks.tsx new file mode 100644 index 00000000..b7c4595b --- /dev/null +++ b/packages/client/pages/[network]/blocks.tsx @@ -0,0 +1,118 @@ +import React, { useState, useEffect, useContext } from 'react'; +import Head from 'next/head'; +import { useRouter } from 'next/router'; + +// Contexts +import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; + +// Axios +import axiosClient from '../../config/axios'; + +// Components +import Layout from '../../components/layouts/Layout'; +import BlocksLayout from '../../components/layouts/BlocksLayout'; +import Loader from '../../components/ui/Loader'; +import ViewMoreButton from '../../components/ui/ViewMoreButton'; + +// Types +import { Slot } from '../../types'; + +const Blocks = () => { + // Theme Mode Context + const { themeMode } = useContext(ThemeModeContext) ?? {}; + + // Router + const router = useRouter(); + const { network } = router.query; + + // States + const [slots, setSlots] = useState([]); + const [currentPage, setCurrentPage] = useState(0); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (network && slots.length === 0) { + getSlots(0); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [network]); + + const getSlots = async (page: number) => { + try { + setLoading(true); + + setCurrentPage(page); + + const response = await axiosClient.get(`/api/slots`, { + params: { + network, + page, + limit: 32, + }, + }); + + setSlots(prevState => [ + ...prevState, + ...response.data.slots.filter( + (slot: Slot) => + !prevState.find((prevSlot: Slot) => prevSlot.f_proposer_slot === slot.f_proposer_slot) + ), + ]); + } catch (error) { + console.log(error); + } finally { + setLoading(false); + } + }; + + return ( + + + Blocks of the Ethereum Chain - EthSeer.io + + + + + +

+ Ethereum Blocks +

+ +
+

+ Blocks are the fundamental unit of consensus for blockchains. In it you will find a number of + transactions and interactions with smart contracts. +

+
+ +
{slots.length > 0 && }
+ + {loading && ( +
+ +
+ )} + + {slots.length > 0 && getSlots(currentPage + 1)} />} +
+ ); +}; + +export default Blocks; From 540f7b966dce61845f4fe50e61988196ef7bdf2f Mon Sep 17 00:00:00 2001 From: Marina Terentii <125751323+Artentii@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:10:41 +0100 Subject: [PATCH 07/40] Added card example to entites page --- packages/client/components/ui/EntityCard.tsx | 5 ++- packages/client/pages/[network]/entities.tsx | 34 +++++++++++++++++- .../static/images/blocks/cubes/example.webp | Bin 0 -> 1760 bytes 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 packages/client/public/static/images/blocks/cubes/example.webp diff --git a/packages/client/components/ui/EntityCard.tsx b/packages/client/components/ui/EntityCard.tsx index f5688309..c0112abe 100644 --- a/packages/client/components/ui/EntityCard.tsx +++ b/packages/client/components/ui/EntityCard.tsx @@ -19,11 +19,10 @@ const EntityCard = ({ index, pool }: Props) => { return (
diff --git a/packages/client/pages/[network]/entities.tsx b/packages/client/pages/[network]/entities.tsx index ea2ccdf0..2a6e31e6 100644 --- a/packages/client/pages/[network]/entities.tsx +++ b/packages/client/pages/[network]/entities.tsx @@ -7,6 +7,7 @@ import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; // Components import Layout from '../../components/layouts/Layout'; import EntityCard from '../../components/ui/EntityCard'; +import CustomImage from '../../components/ui/CustomImage'; // Constants import { useRouter } from 'next/router'; @@ -84,7 +85,7 @@ const Entities = () => { style={{ background: themeMode?.darkMode ? 'var(--bgDarkMode)' : 'var(--bgMainLightMode)' }} >

{ deposit address analysis, among others. EthSeer also monitors their performance.

+
+
+

+ This is a card example with the entity information you'll find: +

+
+ +
+ Entity name + ordered by + Number of validators +
+
+
+
{loading && (
diff --git a/packages/client/public/static/images/blocks/cubes/example.webp b/packages/client/public/static/images/blocks/cubes/example.webp new file mode 100644 index 0000000000000000000000000000000000000000..de8583bb329c2fba58c89c53658d9f41530ad146 GIT binary patch literal 1760 zcmV<61|RuSNk&H41^@t8MM6+kP&iD?1^@srW55^?Z^O848%gfJqwQ*6N#=00XAg|p zMv_$g87-@Q^*PmH)Haf&;?HPV?W@nJ4uVLwvCV!aQ8ggJdlC?kBs3s*NOJ!Fm(*{P zo}@~uq)IxHDycvK007`m`3->d9RQ&30KUD1pG(sHvHC8f8^E^F4Pe`x(RcQH_Ivhw zY_o07unjP}*_N3AfQZm{s!HDhjBd68=8Uxqu-`Mf*;Z8mfQWE>--?LI%*_!9BCwf^b#s0|OYfiE&Q`Y7A`GHI)*xgXrh;5QYBao#%X3xd%-k3(g}_@#T@-Vbih z51R_maR?bH z#*&QzlvrOrEf$MyUHALtt~TK6`3Zw2?F*c-mIM2QLA>&Du@I{3{(QS9k)1ai3YthK ztir|Kdsad=s};G{V)q~sXGFW!EfY${$gS`fizaQ4ogjLdiCoyoRvY|j)OEj~MZGK_ z=j`>`N(=l6)k*rnTK#WUU<3RXNeA_QK<)6SQayH$o0p(aBof*%A@9)_hiq1NiU9%HW3(Yed>tGT7jsSTvZ@ULXw6m)M8`q!v z6P*4pFb9V>fDp{r+?p{XD&h=%L}O34 zYSWYD$pBT(l#0u)Jj-h!r8Q`8gv>=tR3Sp7jaf*{$>~aFDNA&|B?Zt==*~kATJfqX z=JS%Axqqc;V8BV-Nt{L0pr9q{(U@TlE^DqVhieIKaRY>IQ33D>*@ELxhS(JIOx;cH zT`?a!7abwS!lMjhoFQ2nM|hOLv4I7rxcYO9oG-|=ghaFN%0af2^%YTbBOQ8GLoCQE z*9bJl6Nl)on%{wl93<9o5G<66m1HgLhzZS~8=!a|O$^tF5L;)?T&tV~N~C&FMJu9Y zD&`UbU9Qw%i5I%$j5o*W z=Q0>w&7wwG4?b5D!2)0owwn8o_0^bpMC7!<=p}H8+DUuAkLfH9M4-A-&2R_K0qL)L zK(slt6@Y=Y zHDgOOo9@mCRE~9=D$HIomGZ0)tLr0lh%C^1(M9QsISkv%UT9p3!R<73!(My&2OI_(?@UiDt`C;;n95X+1Dq5-@SZvAn$+j)k(WvJvoN=KK|llRJ+S-W@CalkPfraHQ}J*1>;!gnt6a CCUZRi literal 0 HcmV?d00001 From 95400097f7fa15216fa0610f021b03bfd352c802 Mon Sep 17 00:00:00 2001 From: Marina Terentii <125751323+Artentii@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:14:47 +0100 Subject: [PATCH 08/40] Adjusted all pages line-height of intro text --- packages/client/pages/[network]/blocks.tsx | 2 +- packages/client/pages/[network]/epochs.tsx | 2 +- packages/client/pages/[network]/index.tsx | 2 +- packages/client/pages/[network]/slots.tsx | 2 +- packages/client/pages/[network]/validators.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/client/pages/[network]/blocks.tsx b/packages/client/pages/[network]/blocks.tsx index b7c4595b..7018acee 100644 --- a/packages/client/pages/[network]/blocks.tsx +++ b/packages/client/pages/[network]/blocks.tsx @@ -92,7 +92,7 @@ const Blocks = () => { style={{ background: themeMode?.darkMode ? 'var(--bgDarkMode)' : 'var(--bgMainLightMode)' }} >

{ style={{ background: themeMode?.darkMode ? 'var(--bgDarkMode)' : 'var(--bgMainLightMode)' }} >

{ style={{ background: themeMode?.darkMode ? 'var(--bgDarkMode)' : 'var(--bgMainLightMode)' }} >

{ style={{ background: themeMode?.darkMode ? 'var(--bgDarkMode)' : 'var(--bgMainLightMode)' }} >

Date: Wed, 8 Nov 2023 13:55:12 +0100 Subject: [PATCH 09/40] support transactions --- .../client/pages/[network]/block/[id].tsx | 79 ++++++++++++------- packages/client/types/index.ts | 10 +++ packages/server/controllers/blocks.ts | 34 ++++++++ packages/server/routes/blocks.ts | 8 +- 4 files changed, 100 insertions(+), 31 deletions(-) diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index 89163a0a..a24ac586 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -24,7 +24,7 @@ import ValidatorStatus from '../../../components/ui/ValidatorStatus'; import LinkBlock from '../../../components/ui/LinkBlock'; // Types -import { BlockEL, Withdrawal } from '../../../types'; +import { BlockEL, Transaction, Withdrawal } from '../../../types'; type CardProps = { title: string; @@ -82,11 +82,13 @@ const BlockPage = () => { // States const [block, setBlock] = useState(null); const [withdrawals, setWithdrawals] = useState>([]); + const [transactions, setTransactions] = useState>([]); const [existsBlock, setExistsBlock] = useState(true); const [countdownText, setCountdownText] = useState(''); const [tabPageIndex, setTabPageIndex] = useState(0); const [loadingBlock, setLoadingBlock] = useState(true); const [loadingWithdrawals, setLoadingWithdrawals] = useState(true); + const [loadingTransactions, setLoadingTransactions] = useState(true); const [desktopView, setDesktopView] = useState(true); const [blockGenesis, setBlockGenesis] = useState(0); @@ -98,7 +100,7 @@ const BlockPage = () => { if (network && ((id && !block) || (block && block.f_slot !== Number(id)))) { getBlock(); - getWithdrawals(); + getTransactions(); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -183,22 +185,22 @@ const BlockPage = () => { } }; - // Get withdrawals - const getWithdrawals = async () => { + // Get transactions + const getTransactions = async () => { try { - setLoadingWithdrawals(true); + setLoadingTransactions(true); - const response = await axiosClient.get(`/api/slots/${id}/withdrawals`, { + const response = await axiosClient.get(`/api/blocks/${id}/transactions`, { params: { network, }, }); - setWithdrawals(response.data.withdrawals); + setTransactions(response.data.transactions); } catch (error) { console.log(error); } finally { - setLoadingWithdrawals(false); + setLoadingTransactions(false); } }; @@ -338,6 +340,23 @@ const BlockPage = () => { ); }; + const timeSince = (timestamp: number) => { + const now = new Date(); + const then = new Date(timestamp); + const diff = now.getTime() - then.getTime(); + + const minutes = Math.floor(diff / 60000); + const hours = Math.floor(minutes / 60); + const remainingMinutes = minutes % 60; + + if (hours === 0) { + return `${remainingMinutes} mins ago`; + } else { + return `${hours} hrs ${remainingMinutes} mins ago`; + } + + } + //Transactions tab - table desktop const getTransactionsDesktop = () => { return ( @@ -486,7 +505,7 @@ const BlockPage = () => {

- {loadingWithdrawals ? ( + {loadingTransactions ? (
@@ -499,36 +518,36 @@ const BlockPage = () => { color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} > - {withdrawals.map(element => ( + {transactions.map(element => (
-

{getShortAddress(element?.f_address)}

+

{getShortAddress(element?.f_hash)}

- +

{element.f_tx_type}

-

{'9hrs 51mins ago'}

+

{timeSince(element.f_timestamp *1000)}

-

{getShortAddress(element?.f_address)}

+

{getShortAddress(element.f_from)}

-

{getShortAddress(element?.f_address)}

+

{getShortAddress(element.f_to)}

-

{(element.f_amount / 10 ** 9).toLocaleString()} ETH

-

{(element.f_amount / 10 ** 9).toLocaleString()}

+

{(element.f_value / 10 ** 18).toLocaleString()} ETH

+

{(element.f_gas_fee_cap / 10 ** 18).toLocaleString()}

))} - {withdrawals.length == 0 && ( + {transactions.length == 0 && (

No transactions

@@ -550,13 +569,13 @@ const BlockPage = () => { }} onMouseMove={handleMouseMove} > - {loadingWithdrawals ? ( + {loadingTransactions ? (
) : (
- {withdrawals.map(element => ( + {transactions.map(element => (
{ : 'var(--boxShadowCardLight)', color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} - key={element.f_val_idx} + key={element.f_hash} >

{ > Txn Hash

-

{getShortAddress(element?.f_address)}

+

{getShortAddress(element?.f_hash)}

{ > Method

- +

{element.f_tx_type}

{ > Age

-

{'9hrs 51mins ago'}

+

{timeSince(element.f_timestamp*1000)}

{

-

{getShortAddress(element?.f_address)}

+

{getShortAddress(element?.f_from)}

{ height={20} />
-

{getShortAddress(element?.f_address)}

+

{getShortAddress(element?.f_to)}

@@ -644,7 +663,7 @@ const BlockPage = () => { > Value

-

{(element.f_amount / 10 ** 9).toLocaleString()} ETH

+

{(element.f_value / 10 ** 18).toLocaleString()} ETH

{ > Txn Fee

-

{(element.f_amount / 10 ** 9).toLocaleString()}

+

{(element.f_gas_fee_cap / 10 ** 18).toLocaleString()}

))} - {withdrawals.length == 0 && ( + {transactions.length == 0 && (
{ }, }); + } catch (error) { + console.log(error); + return res.status(500).json({ + msg: 'An error occurred on the server' + }); + } +}; + +export const getTransactionsByBlock = async (req: Request, res: Response) => { + + try { + + const { id } = req.params; + const { network } = req.query; + + const pgPool = pgPools[network as string]; + + const transactions = + await pgPool.query(` + SELECT f_tx_type, + f_value, + f_gas_fee_cap, + f_to, + f_hash, + f_timestamp, + f_from + FROM t_transactions + WHERE f_el_block_number = '${id}' + `); + + res.json({ + transactions: transactions.rows, + }); + } catch (error) { console.log(error); return res.status(500).json({ diff --git a/packages/server/routes/blocks.ts b/packages/server/routes/blocks.ts index dee382e2..ea924049 100644 --- a/packages/server/routes/blocks.ts +++ b/packages/server/routes/blocks.ts @@ -2,7 +2,7 @@ import { Router } from 'express'; import { check, query } from 'express-validator'; import { - getBlockById, + getBlockById, getTransactionsByBlock, } from '../controllers/blocks'; import { checkFields } from '../middlewares/check-fields'; @@ -17,6 +17,12 @@ router.get('/:id', [ query('network').custom(existsNetwork), checkFields, ], getBlockById); +router.get('/:id/transactions', [ + check('id').isInt({ min: 0, max: 2147483647 }), + query('network').not().isEmpty(), + query('network').custom(existsNetwork), + checkFields, +], getTransactionsByBlock); export default router; \ No newline at end of file From 4011bfed3dea8decaec517fa055c91aae8e72b2a Mon Sep 17 00:00:00 2001 From: Marina Terentii <125751323+Artentii@users.noreply.github.com> Date: Fri, 10 Nov 2023 10:54:25 +0100 Subject: [PATCH 10/40] Descriptions in transactions tooltips added --- .../components/ui/TooltipResponsive.tsx | 2 +- .../client/pages/[network]/block/[id].tsx | 75 ++++++------------- packages/client/types/index.ts | 14 ++-- 3 files changed, 30 insertions(+), 61 deletions(-) diff --git a/packages/client/components/ui/TooltipResponsive.tsx b/packages/client/components/ui/TooltipResponsive.tsx index e225a250..4f6a235e 100644 --- a/packages/client/components/ui/TooltipResponsive.tsx +++ b/packages/client/components/ui/TooltipResponsive.tsx @@ -39,7 +39,7 @@ const TooltipResponsive = ({ width, backgroundColor, colorLetter, content, top, return (
{ const now = new Date(); const then = new Date(timestamp); const diff = now.getTime() - then.getTime(); - + const minutes = Math.floor(diff / 60000); const hours = Math.floor(minutes / 60); const remainingMinutes = minutes % 60; - + if (hours === 0) { return `${remainingMinutes} mins ago`; } else { return `${hours} hrs ${remainingMinutes} mins ago`; } - - } + }; //Transactions tab - table desktop const getTransactionsDesktop = () => { return ( -
+
{ }} >
-

Txn Hash

+

Txn Hash

{ colorLetter='black' content={ <> - Time at which the slot - should have passed - (calculated since genesis) + + A transaction hash, often denoted as TXN Hash, serves as a distinctive + 66-character identifier produced each time a transaction is executed. + } top='34px' @@ -413,9 +409,10 @@ const BlockPage = () => { colorLetter='black' content={ <> - Time at which the slot - should have passed - (calculated since genesis) + + A function is executed depending on the decoded input data. In cases where + the functions are not recognized, the method ID is presented instead. + } top='34px' @@ -439,9 +436,7 @@ const BlockPage = () => { colorLetter='black' content={ <> - Time at which the slot - should have passed - (calculated since genesis) + Time has passed since it was created. } top='34px' @@ -453,29 +448,6 @@ const BlockPage = () => {

To

Value

- - - - - Time at which the slot - should have passed - (calculated since genesis) - - } - top='34px' - polygonLeft - /> -

Txn Fee

@@ -488,14 +460,12 @@ const BlockPage = () => { /> - Time at which the slot - should have passed - (calculated since genesis) + (Gas price*Gas used by Txns) in Ether } top='34px' @@ -519,19 +489,16 @@ const BlockPage = () => { }} > {transactions.map(element => ( -
+

{getShortAddress(element?.f_hash)}

-

{element.f_tx_type}

+

{element.f_tx_type}

-

{timeSince(element.f_timestamp *1000)}

+

{timeSince(element.f_timestamp * 1000)}

{getShortAddress(element.f_from)}

{ > Age

-

{timeSince(element.f_timestamp*1000)}

+

+ {timeSince(element.f_timestamp * 1000)} +

Date: Fri, 10 Nov 2023 11:09:56 +0100 Subject: [PATCH 11/40] Changed send icon color --- packages/client/pages/[network]/block/[id].tsx | 4 ++-- .../public/static/images/icons/send_dark.webp | Bin 980 -> 924 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index bc1882ec..62a90e5b 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -504,8 +504,8 @@ const BlockPage = () => {

{getShortAddress(element.f_to)}

diff --git a/packages/client/public/static/images/icons/send_dark.webp b/packages/client/public/static/images/icons/send_dark.webp index c8952d9a81ec4796c949864337b367048e62f1ef..6301d1e0503c36e606f87533695f520786d81de4 100644 GIT binary patch literal 924 zcmV;N17rMBNk&GL0{{S5MM6+kP&iD80{{RoSHKkzH=&?yBgZ%RvpI4;JUrX-}2Nt9ti5sJbO zY7j|*A_Fq6^X@Zh#7IxF&99;8K^}ho6_s}1civ6$0tvP)#f?K_0Ul!7rm?IcR^oC0 zYfPwu`lSU#^xvUuD{;dz7$pQ-!R-$W0tbIs&MEi*FsFD7@4>79-4t`*fH_XOP41R^ z0j5Q(!ur^Y@$5B9yuL^sUhqX}eFaywla4$8ab4F9&P}^xs(RJI-r84&+~RHBv+L(x zX}V$HdS+{OPViga(Us54m4X#^+;i#|xyc(}LJX?})8cIGEa=bLGLG&=urQGZjNvO& z`A5QREfNDm;?@ig-Z|4op|!;1F{GBrm}kRQk)ACsWsWuJ($yhxtV?IxsFc;%m?5_r zxb`K?28E(=;hO4#QN?Wuv89j6=)&eydhZ(~QBu?)25nXDj5=C98^W6A4F~pY^%E z#vI8P?Rxl16Gtu;RalhnF3fzr4(3)@k$(09fdpZb=se

mdv~E-ziFaOyJ(ZDpf#MUENtxieFwi=*r5)Q`{cowHUW7P zlYECOWsivg4j0M&#(~{NoI!t9s9l%68=SR%sq9U57Iz0wzdt%m&JA7Hxwk*m?~kH` yPQ3CxW7X#R3OQ&@ZT=*O=(Q%y;htprPWQ~W{l1pg=r(=AG1B|x)3WoBNzwqfUe8nj literal 980 zcmV;_11tPeNk&G@0{{S5MM6+kP&iD#0{{RoR=^byw}G&cWXwPM)qnVF13}xi@wWe1 z&mTa?xNX}&bc*gjVy8IVD=?!-lEQQpFmXT}L=ygApn!n;-(1sMwcmqud$1dGBPKCO zS%*b3G)TfgQ<}gSROMQ`p;{=Y?V}6^4HbxJR8SH@`*mNwdGMW|qcT_au+p=p{{;yG zfVORKwr?Fa^#}FodRFP(fQe9wWXuI?e`XYg((>)>?JOo zBJ;gvo0Z`+Shz&w%%g_yXQh$Oj6)ZSlS@R%+Kv=2M=eoKQ2FmJ-I6w&S|7EET z1F*urkOoW=hVK0hUGM6S;WAmVQZ^f4TYg;lmZtXuTjew z7CSJa6tmD5zs|s8h9mzyqpD9A+*C8z!k5No56p}4% zdJp%pt{{~e<$QORfDcP&O1U;DC>W5-V9$W940KOMrzvPCQ*>eC*h5W7%iucR$P-Af1_C_CKyin8VX_bP)ZZ@1I5VfhViO^u<1r$oKY2}nmkf*PvJ(!F@>c7Z*ho}9Vcv?CMH3utHO(nI3hYKa z7YP)jS>X(71fxYFf&bW`rj;%>tgQ3%-UnZJutpWhX C4BpQG From 3baef60522d78f99c444f0d9eb45e719ca140403 Mon Sep 17 00:00:00 2001 From: Iuri Date: Sun, 12 Nov 2023 11:39:10 +0100 Subject: [PATCH 12/40] Implement new SEO routing --- packages/client/components/ui/Menu.tsx | 4 +- packages/client/components/ui/NetworkLink.tsx | 6 +- packages/client/middleware.ts | 92 +++++++++---------- .../client/pages/{[network] => }/entities.tsx | 12 +-- .../pages/{[network] => }/entity/[name].tsx | 17 ++-- .../pages/{[network] => }/epoch/[id].tsx | 20 ++-- .../client/pages/{[network] => }/epochs.tsx | 6 +- .../client/pages/{[network] => }/index.tsx | 18 ++-- .../pages/{[network] => }/slot/[id].tsx | 26 +++--- .../slot/graffiti/[graffiti].tsx | 4 +- .../client/pages/{[network] => }/slots.tsx | 14 +-- .../pages/{[network] => }/validator/[id].tsx | 30 +++--- .../pages/{[network] => }/validators.tsx | 18 ++-- 13 files changed, 132 insertions(+), 135 deletions(-) rename packages/client/pages/{[network] => }/entities.tsx (92%) rename packages/client/pages/{[network] => }/entity/[name].tsx (97%) rename packages/client/pages/{[network] => }/epoch/[id].tsx (95%) rename packages/client/pages/{[network] => }/epochs.tsx (91%) rename packages/client/pages/{[network] => }/index.tsx (88%) rename packages/client/pages/{[network] => }/slot/[id].tsx (96%) rename packages/client/pages/{[network] => }/slot/graffiti/[graffiti].tsx (72%) rename packages/client/pages/{[network] => }/slots.tsx (90%) rename packages/client/pages/{[network] => }/validator/[id].tsx (97%) rename packages/client/pages/{[network] => }/validators.tsx (95%) diff --git a/packages/client/components/ui/Menu.tsx b/packages/client/components/ui/Menu.tsx index d033d671..e0f93071 100644 --- a/packages/client/components/ui/Menu.tsx +++ b/packages/client/components/ui/Menu.tsx @@ -18,7 +18,7 @@ const Menu = () => { const [networks, setNetworks] = useState([]); useEffect(() => { - if (!networks || networks.length === 0) { + if (networks.length === 0) { getNetworks(); } @@ -62,7 +62,7 @@ const Menu = () => { ? networks.map((network: string) => { return { name: network.charAt(0).toUpperCase() + network.slice(1), - route: `/${network}`, + route: `/?network=${network}`, }; }) : [], diff --git a/packages/client/components/ui/NetworkLink.tsx b/packages/client/components/ui/NetworkLink.tsx index f24398c5..ab80856c 100644 --- a/packages/client/components/ui/NetworkLink.tsx +++ b/packages/client/components/ui/NetworkLink.tsx @@ -17,16 +17,16 @@ const NetworkLink = ({ children, href, ...rest }: Props) => { let adjustedHref = href; if (typeof href === 'string') { - adjustedHref = network ? `/${network}${href}` : href; + adjustedHref = network ? `${href}?network=${network}` : href; } else if (typeof href === 'object' && href.pathname) { adjustedHref = { ...href, - pathname: network ? `/${network}${href.pathname}` : href.pathname, + pathname: network ? `${href.pathname}?network=${network}` : href.pathname, }; } return ( - + {children} ); diff --git a/packages/client/middleware.ts b/packages/client/middleware.ts index 6786caa9..ec89bb33 100644 --- a/packages/client/middleware.ts +++ b/packages/client/middleware.ts @@ -1,38 +1,28 @@ import { NextRequest, NextResponse } from 'next/server'; -// Cache for the networks -let networksCache: string[] | null = null; -let defaultNetworkCache: string | null = null; - -export async function fetchNetworks() { - if (!networksCache) { - try { - const response = await fetch(`${process.env.API_URL}/api/networks`); - const networksData = await response.json(); - networksCache = networksData.networks; - if ((networksCache as string[]).length > 0) { - defaultNetworkCache = (networksCache as string[])[0]; - } - } catch (err) { - console.error('Error fetching networks:', err); - } +export async function fetchNetworks(): Promise<{ networks: string[]; defaultNetwork: string | null }> { + try { + const response = await fetch(`${process.env.URL_API}/api/networks`); + const networksData = await response.json(); + + return { + networks: networksData.networks, + defaultNetwork: networksData.networks ? networksData.networks[0] : null, + }; + + } catch (err) { + console.error('Error fetching networks:', err); + throw new Error('Error fetching networks'); } - return { networks: networksCache, default_network: defaultNetworkCache }; } export async function middleware(req: NextRequest) { - const { networks, default_network } = await fetchNetworks(); - - const pathsWithoutNetwork = [ - '/entity', - '/entities', - '/epoch', - '/epochs', - '/slot', - '/slots', - '/validator', - '/validators', - ]; + + if (req.nextUrl.pathname.includes('_next') || req.nextUrl.pathname.includes('static')) + return NextResponse.next(); + + const { networks, defaultNetwork } = await fetchNetworks(); + const oldPathsToReplace = [ { singular: 'entity', @@ -56,7 +46,18 @@ export async function middleware(req: NextRequest) { }, ]; - const isPathWithoutNetwork = pathsWithoutNetwork.find(item => req.nextUrl.pathname.startsWith(item)); + let network = req.nextUrl.searchParams.get('network'); + + const networkInsidePath = networks?.find(item => req.nextUrl.pathname.includes(`/${item}/`)); + + let mustRedirect = false; + + if (networkInsidePath) { + network = networkInsidePath; + req.nextUrl.pathname = req.nextUrl.pathname.replace(`/${network}/`, '/'); + req.nextUrl.searchParams.set('network', network); + mustRedirect = true; + } const hasOldPath = oldPathsToReplace.filter( @@ -67,34 +68,31 @@ export async function middleware(req: NextRequest) { !req.nextUrl.pathname.includes('graffitis')) ).length > 0; - const replaceOldPaths = (url: string) => { + const replaceOldPaths = (pathname: string) => { oldPathsToReplace.forEach(item => { - if (url.includes(`/${item.plural}/`)) { - url = url.replace(`/${item.plural}/`, `/${item.singular}/`); - } else if (url.endsWith(`/${item.singular}`) && !url.includes('graffiti') && !url.includes('graffitis')) { - url = url.replace(`/${item.singular}`, `/${item.plural}`); + if (pathname.includes(`/${item.plural}/`)) { + pathname = pathname.replace(`/${item.plural}/`, `/${item.singular}/`); + } else if (pathname.endsWith(`/${item.singular}`) && !pathname.includes('graffiti') && !pathname.includes('graffitis')) { + pathname = pathname.replace(`/${item.singular}`, `/${item.plural}`); } }); - return url; + return pathname; }; - if (isPathWithoutNetwork || req.nextUrl.pathname === '/') { - let newUrl = `${req.nextUrl.origin}/${default_network}${req.nextUrl.pathname}`; + if (!network || !networks?.includes(network)) { + req.nextUrl.searchParams.set('network', defaultNetwork as string); if (hasOldPath) { - newUrl = replaceOldPaths(newUrl); + req.nextUrl.pathname = replaceOldPaths(req.nextUrl.pathname); } - return NextResponse.redirect(newUrl); + return NextResponse.redirect(req.nextUrl); } else if (hasOldPath) { - return NextResponse.redirect(replaceOldPaths(req.nextUrl.href)); - } else if (!req.nextUrl.pathname.includes('_next') && !req.nextUrl.pathname.includes('static')) { - const network = req.nextUrl.pathname.split('/')[1]; - - if (!networks?.includes(network)) { - return NextResponse.redirect(`${req.nextUrl.origin}/${default_network}`); - } + req.nextUrl.pathname = replaceOldPaths(req.nextUrl.pathname); + return NextResponse.redirect(req.nextUrl); + } else if (mustRedirect) { + return NextResponse.redirect(req.nextUrl); } return NextResponse.next(); diff --git a/packages/client/pages/[network]/entities.tsx b/packages/client/pages/entities.tsx similarity index 92% rename from packages/client/pages/[network]/entities.tsx rename to packages/client/pages/entities.tsx index 29994bad..874fedca 100644 --- a/packages/client/pages/[network]/entities.tsx +++ b/packages/client/pages/entities.tsx @@ -2,17 +2,17 @@ import React, { useContext, useEffect, useState } from 'react'; import Head from 'next/head'; // Contexts -import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; +import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; // Components -import Layout from '../../components/layouts/Layout'; -import EntityCard from '../../components/ui/EntityCard'; +import Layout from '../components/layouts/Layout'; +import EntityCard from '../components/ui/EntityCard'; // Constants import { useRouter } from 'next/router'; -import axiosClient from '../../config/axios'; -import Loader from '../../components/ui/Loader'; -import Animation from '../../components/layouts/Animation'; +import axiosClient from '../config/axios'; +import Loader from '../components/ui/Loader'; +import Animation from '../components/layouts/Animation'; type Entity = { f_pool_name: string; diff --git a/packages/client/pages/[network]/entity/[name].tsx b/packages/client/pages/entity/[name].tsx similarity index 97% rename from packages/client/pages/[network]/entity/[name].tsx rename to packages/client/pages/entity/[name].tsx index 83d3385e..66b81299 100644 --- a/packages/client/pages/[network]/entity/[name].tsx +++ b/packages/client/pages/entity/[name].tsx @@ -2,21 +2,20 @@ import React, { useContext, useEffect, useState } from 'react'; import { useRouter } from 'next/router'; // Axios -import axiosClient from '../../../config/axios'; +import axiosClient from '../../config/axios'; // Contexts -import ThemeModeContext from '../../../contexts/theme-mode/ThemeModeContext'; +import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; // Components -import Layout from '../../../components/layouts/Layout'; -import BlockGif from '../../../components/ui/BlockGif'; -import Animation from '../../../components/layouts/Animation'; -import Loader from '../../../components/ui/Loader'; -import ProgressSmoothBar from '../../../components/ui/ProgressSmoothBar'; -import TabHeader from '../../../components/ui/TabHeader'; +import Layout from '../../components/layouts/Layout'; +import Animation from '../../components/layouts/Animation'; +import Loader from '../../components/ui/Loader'; +import ProgressSmoothBar from '../../components/ui/ProgressSmoothBar'; +import TabHeader from '../../components/ui/TabHeader'; // Types -import { Entity } from '../../../types'; +import { Entity } from '../../types'; type Props = { content: string; diff --git a/packages/client/pages/[network]/epoch/[id].tsx b/packages/client/pages/epoch/[id].tsx similarity index 95% rename from packages/client/pages/[network]/epoch/[id].tsx rename to packages/client/pages/epoch/[id].tsx index 6538223e..e0cc7633 100644 --- a/packages/client/pages/[network]/epoch/[id].tsx +++ b/packages/client/pages/epoch/[id].tsx @@ -3,22 +3,22 @@ import { useRouter } from 'next/router'; import Head from 'next/head'; // Axios -import axiosClient from '../../../config/axios'; +import axiosClient from '../../config/axios'; // Contexts -import ThemeModeContext from '../../../contexts/theme-mode/ThemeModeContext'; +import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; // Components -import Layout from '../../../components/layouts/Layout'; -import ProgressSmoothBar from '../../../components/ui/ProgressSmoothBar'; -import EpochAnimation from '../../../components/layouts/EpochAnimation'; -import Loader from '../../../components/ui/Loader'; -import LinkEpoch from '../../../components/ui/LinkEpoch'; -import Slots from '../../../components/layouts/Slots'; -import Arrow from '../../../components/ui/Arrow'; +import Layout from '../../components/layouts/Layout'; +import ProgressSmoothBar from '../../components/ui/ProgressSmoothBar'; +import EpochAnimation from '../../components/layouts/EpochAnimation'; +import Loader from '../../components/ui/Loader'; +import LinkEpoch from '../../components/ui/LinkEpoch'; +import Slots from '../../components/layouts/Slots'; +import Arrow from '../../components/ui/Arrow'; // Types -import { Epoch, Slot } from '../../../types'; +import { Epoch, Slot } from '../../types'; type Props = { content: string; diff --git a/packages/client/pages/[network]/epochs.tsx b/packages/client/pages/epochs.tsx similarity index 91% rename from packages/client/pages/[network]/epochs.tsx rename to packages/client/pages/epochs.tsx index 4bced28a..8979fb38 100644 --- a/packages/client/pages/[network]/epochs.tsx +++ b/packages/client/pages/epochs.tsx @@ -2,11 +2,11 @@ import React, { useContext } from 'react'; import Head from 'next/head'; // Contexts -import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; +import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; // Components -import Layout from '../../components/layouts/Layout'; -import Statitstics from '../../components/layouts/Statitstics'; +import Layout from '../components/layouts/Layout'; +import Statitstics from '../components/layouts/Statitstics'; const Epochs = () => { // Theme Mode Context diff --git a/packages/client/pages/[network]/index.tsx b/packages/client/pages/index.tsx similarity index 88% rename from packages/client/pages/[network]/index.tsx rename to packages/client/pages/index.tsx index f965edbb..99b97007 100644 --- a/packages/client/pages/[network]/index.tsx +++ b/packages/client/pages/index.tsx @@ -2,17 +2,17 @@ import React, { useContext, useEffect, useState } from 'react'; import { useRouter } from 'next/router'; // Context -import StatusContext from '../../contexts/status/StatusContext'; -import BlocksContext from '../../contexts/blocks/BlocksContext'; -import EpochsContext from '../../contexts/epochs/EpochsContext'; -import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; +import StatusContext from '../contexts/status/StatusContext'; +import BlocksContext from '../contexts/blocks/BlocksContext'; +import EpochsContext from '../contexts/epochs/EpochsContext'; +import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; // Components -import Layout from '../../components/layouts/Layout'; -import ChainOverview from '../../components/layouts/ChainOverview'; -import Statitstics from '../../components/layouts/Statitstics'; -import Problems from '../../components/layouts/Problems'; -import SummaryOverview from '../../components/ui/SummaryOverview'; +import Layout from '../components/layouts/Layout'; +import ChainOverview from '../components/layouts/ChainOverview'; +import Statitstics from '../components/layouts/Statitstics'; +import Problems from '../components/layouts/Problems'; +import SummaryOverview from '../components/ui/SummaryOverview'; export default function Home() { // Router diff --git a/packages/client/pages/[network]/slot/[id].tsx b/packages/client/pages/slot/[id].tsx similarity index 96% rename from packages/client/pages/[network]/slot/[id].tsx rename to packages/client/pages/slot/[id].tsx index 89420382..aecf5c96 100644 --- a/packages/client/pages/[network]/slot/[id].tsx +++ b/packages/client/pages/slot/[id].tsx @@ -3,27 +3,27 @@ import { useRouter } from 'next/router'; import Head from 'next/head'; // Axios -import axiosClient from '../../../config/axios'; +import axiosClient from '../../config/axios'; // Contexts -import ThemeModeContext from '../../../contexts/theme-mode/ThemeModeContext'; +import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; // Components -import Layout from '../../../components/layouts/Layout'; -import TabHeader from '../../../components/ui/TabHeader'; -import Loader from '../../../components/ui/Loader'; -import LinkValidator from '../../../components/ui/LinkValidator'; -import LinkSlot from '../../../components/ui/LinkSlot'; -import Arrow from '../../../components/ui/Arrow'; -import LinkEpoch from '../../../components/ui/LinkEpoch'; -import LinkEntity from '../../../components/ui/LinkEntity'; +import Layout from '../../components/layouts/Layout'; +import TabHeader from '../../components/ui/TabHeader'; +import Loader from '../../components/ui/Loader'; +import LinkValidator from '../../components/ui/LinkValidator'; +import LinkSlot from '../../components/ui/LinkSlot'; +import Arrow from '../../components/ui/Arrow'; +import LinkEpoch from '../../components/ui/LinkEpoch'; +import LinkEntity from '../../components/ui/LinkEntity'; // Types -import { Block, Withdrawal } from '../../../types'; +import { Block, Withdrawal } from '../../types'; // Constants -import { ADDRESS_ZERO, ADDRESS_ZERO_SHORT } from '../../../constants'; -import EpochAnimation from '../../../components/layouts/EpochAnimation'; +import { ADDRESS_ZERO, ADDRESS_ZERO_SHORT } from '../../constants'; +import EpochAnimation from '../../components/layouts/EpochAnimation'; type CardProps = { title: string; diff --git a/packages/client/pages/[network]/slot/graffiti/[graffiti].tsx b/packages/client/pages/slot/graffiti/[graffiti].tsx similarity index 72% rename from packages/client/pages/[network]/slot/graffiti/[graffiti].tsx rename to packages/client/pages/slot/graffiti/[graffiti].tsx index 25d91e45..2d829c5c 100644 --- a/packages/client/pages/[network]/slot/graffiti/[graffiti].tsx +++ b/packages/client/pages/slot/graffiti/[graffiti].tsx @@ -2,8 +2,8 @@ import React from 'react'; import Head from 'next/head'; // Components -import Layout from '../../../../components/layouts/Layout'; -import Graffitis from '../../../../components/layouts/Graffitis'; +import Layout from '../../../components/layouts/Layout'; +import Graffitis from '../../../components/layouts/Graffitis'; const SlotGraffitiSearch = () => { return ( diff --git a/packages/client/pages/[network]/slots.tsx b/packages/client/pages/slots.tsx similarity index 90% rename from packages/client/pages/[network]/slots.tsx rename to packages/client/pages/slots.tsx index 5b9c0546..99d5efbc 100644 --- a/packages/client/pages/[network]/slots.tsx +++ b/packages/client/pages/slots.tsx @@ -3,19 +3,19 @@ import Head from 'next/head'; import { useRouter } from 'next/router'; // Contexts -import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; +import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; // Axios -import axiosClient from '../../config/axios'; +import axiosClient from '../config/axios'; // Components -import Layout from '../../components/layouts/Layout'; -import SlotsList from '../../components/layouts/Slots'; -import Loader from '../../components/ui/Loader'; -import ViewMoreButton from '../../components/ui/ViewMoreButton'; +import Layout from '../components/layouts/Layout'; +import SlotsList from '../components/layouts/Slots'; +import Loader from '../components/ui/Loader'; +import ViewMoreButton from '../components/ui/ViewMoreButton'; // Types -import { Slot } from '../../types'; +import { Slot } from '../types'; const Slots = () => { // Theme Mode Context diff --git a/packages/client/pages/[network]/validator/[id].tsx b/packages/client/pages/validator/[id].tsx similarity index 97% rename from packages/client/pages/[network]/validator/[id].tsx rename to packages/client/pages/validator/[id].tsx index d5ddea65..c2517770 100644 --- a/packages/client/pages/[network]/validator/[id].tsx +++ b/packages/client/pages/validator/[id].tsx @@ -3,27 +3,27 @@ import { useRouter } from 'next/router'; import Head from 'next/head'; // Axios -import axiosClient from '../../../config/axios'; +import axiosClient from '../../config/axios'; // Contexts -import ThemeModeContext from '../../../contexts/theme-mode/ThemeModeContext'; +import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; // Components -import Layout from '../../../components/layouts/Layout'; -import BlockImage from '../../../components/ui/BlockImage'; -import TabHeader from '../../../components/ui/TabHeader'; -import Animation from '../../../components/layouts/Animation'; -import ProgressSmoothBar from '../../../components/ui/ProgressSmoothBar'; -import Loader from '../../../components/ui/Loader'; -import ValidatorStatus from '../../../components/ui/ValidatorStatus'; -import LinkEpoch from '../../../components/ui/LinkEpoch'; -import LinkSlot from '../../../components/ui/LinkSlot'; -import LinkEntity from '../../../components/ui/LinkEntity'; -import LinkValidator from '../../../components/ui/LinkValidator'; -import Arrow from '../../../components/ui/Arrow'; +import Layout from '../../components/layouts/Layout'; +import BlockImage from '../../components/ui/BlockImage'; +import TabHeader from '../../components/ui/TabHeader'; +import Animation from '../../components/layouts/Animation'; +import ProgressSmoothBar from '../../components/ui/ProgressSmoothBar'; +import Loader from '../../components/ui/Loader'; +import ValidatorStatus from '../../components/ui/ValidatorStatus'; +import LinkEpoch from '../../components/ui/LinkEpoch'; +import LinkSlot from '../../components/ui/LinkSlot'; +import LinkEntity from '../../components/ui/LinkEntity'; +import LinkValidator from '../../components/ui/LinkValidator'; +import Arrow from '../../components/ui/Arrow'; // Types -import { Validator, Slot, Withdrawal } from '../../../types'; +import { Validator, Slot, Withdrawal } from '../../types'; type Props = { content: string; diff --git a/packages/client/pages/[network]/validators.tsx b/packages/client/pages/validators.tsx similarity index 95% rename from packages/client/pages/[network]/validators.tsx rename to packages/client/pages/validators.tsx index 060c344b..70e3d0ed 100644 --- a/packages/client/pages/[network]/validators.tsx +++ b/packages/client/pages/validators.tsx @@ -3,21 +3,21 @@ import Head from 'next/head'; import { useRouter } from 'next/router'; // Axios -import axiosClient from '../../config/axios'; +import axiosClient from '../config/axios'; // Contexts -import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; +import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; // Components -import Layout from '../../components/layouts/Layout'; -import ValidatorStatus from '../../components/ui/ValidatorStatus'; -import Loader from '../../components/ui/Loader'; -import ViewMoreButton from '../../components/ui/ViewMoreButton'; -import LinkValidator from '../../components/ui/LinkValidator'; -import LinkEntity from '../../components/ui/LinkEntity'; +import Layout from '../components/layouts/Layout'; +import ValidatorStatus from '../components/ui/ValidatorStatus'; +import Loader from '../components/ui/Loader'; +import ViewMoreButton from '../components/ui/ViewMoreButton'; +import LinkValidator from '../components/ui/LinkValidator'; +import LinkEntity from '../components/ui/LinkEntity'; // Types -import { Validator } from '../../types'; +import { Validator } from '../types'; const Validators = () => { // Theme Mode Context From 89bd577492db74a7c63ac641d0f14fbeb305e2b5 Mon Sep 17 00:00:00 2001 From: mpujadas Date: Mon, 20 Nov 2023 20:24:39 +0100 Subject: [PATCH 13/40] Add copy button --- .../client/pages/[network]/block/[id].tsx | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index 62a90e5b..8793f2c0 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -355,6 +355,24 @@ const BlockPage = () => { return `${hours} hrs ${remainingMinutes} mins ago`; } }; + + //CopyAddress + type ElementType = { + f_hash?: string; + f_from?: string; + f_to?: string; + }; + + const handleCopyClick = (element: ElementType, property: 'f_hash' | 'f_from' | 'f_to') => { + const copyText = element[property] || ''; + navigator.clipboard.writeText(copyText) + .then(() => { + console.log('Copy'); + }) + .catch(err => { + console.error('Error', err); + }); + }; //Transactions tab - table desktop const getTransactionsDesktop = () => { @@ -491,7 +509,15 @@ const BlockPage = () => { {transactions.map(element => (

-

{getShortAddress(element?.f_hash)}

+

{getShortAddress(element?.f_hash)} + handleCopyClick(element, 'f_hash')} + /> +

@@ -500,14 +526,30 @@ const BlockPage = () => {

{timeSince(element.f_timestamp * 1000)}

-

{getShortAddress(element.f_from)}

+

{getShortAddress(element.f_from)} + handleCopyClick(element, 'f_from')} + /> +

-

{getShortAddress(element.f_to)}

+

{getShortAddress(element.f_to)} + handleCopyClick(element, 'f_to')} + /> +

{(element.f_value / 10 ** 18).toLocaleString()} ETH

{(element.f_gas_fee_cap / 10 ** 18).toLocaleString()}

From ae66bc956083b11013b038e8923ea0c112d8b3c6 Mon Sep 17 00:00:00 2001 From: mpujadas Date: Tue, 21 Nov 2023 20:23:01 +0100 Subject: [PATCH 14/40] Add styles copy icon --- packages/client/components/ui/CustomImage.tsx | 4 +- .../client/pages/[network]/block/[id].tsx | 126 +++++++++--------- 2 files changed, 68 insertions(+), 62 deletions(-) diff --git a/packages/client/components/ui/CustomImage.tsx b/packages/client/components/ui/CustomImage.tsx index f736a0ac..844ec649 100644 --- a/packages/client/components/ui/CustomImage.tsx +++ b/packages/client/components/ui/CustomImage.tsx @@ -8,10 +8,11 @@ type Props = { height: number; className?: string; priority?: boolean; + title?: string; onClick?: () => void; }; -const CustomImage = ({ src, alt, width, height, className, priority, onClick }: Props) => { +const CustomImage = ({ src, alt, width, height, className, priority, title, onClick }: Props) => { const assetPrefix = process.env.NEXT_PUBLIC_ASSET_PREFIX ?? ''; return ( @@ -22,6 +23,7 @@ const CustomImage = ({ src, alt, width, height, className, priority, onClick }: height={height} className={className} priority={priority} + title={title} onClick={onClick} /> ); diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index 8793f2c0..ac4ad450 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -355,23 +355,34 @@ const BlockPage = () => { return `${hours} hrs ${remainingMinutes} mins ago`; } }; - + //CopyAddress type ElementType = { f_hash?: string; f_from?: string; f_to?: string; - }; + }; - const handleCopyClick = (element: ElementType, property: 'f_hash' | 'f_from' | 'f_to') => { + const [copied, setCopied] = useState<{ [key: string]: boolean | undefined }>({}); + + const handleCopyClick = (element: ElementType, property: 'f_hash' | 'f_from' | 'f_to') => { const copyText = element[property] || ''; - navigator.clipboard.writeText(copyText) - .then(() => { - console.log('Copy'); - }) - .catch(err => { - console.error('Error', err); - }); + navigator.clipboard + .writeText(copyText) + .then(() => { + console.log('Copied'); + setCopied(prevState => { + return { ...(prevState ?? {}), [element.f_hash || '']: true }; + }); + setTimeout(() => { + setCopied(prevState => { + return { ...(prevState ?? {}), [element.f_hash || '']: false }; + }); + }, 2000); + }) + .catch(err => { + console.error('Error', err); + }); }; //Transactions tab - table desktop @@ -508,49 +519,53 @@ const BlockPage = () => { > {transactions.map(element => (
-
-

{getShortAddress(element?.f_hash)} +

+

{getShortAddress(element?.f_hash)}

handleCopyClick(element, 'f_hash')} - /> -

+ className='cursor cursor-pointer' + src={`/static/images/icons/copy_${themeMode?.darkMode ? 'dark' : 'light'}.webp`} + alt='Copy icon' + width={18} + height={18} + title={copied[element.f_hash || ''] ? 'Copied!' : ''} + onClick={() => handleCopyClick(element, 'f_hash')} + />
-
-

{element.f_tx_type}

-
+

{element.f_tx_type}

-

{timeSince(element.f_timestamp * 1000)}

+

{timeSince(element.f_timestamp * 1000)}

-

{getShortAddress(element.f_from)} - handleCopyClick(element, 'f_from')} - /> -

+
+

{getShortAddress(element.f_from)}

+ handleCopyClick(element, 'f_from')} + /> +
-

{getShortAddress(element.f_to)} - handleCopyClick(element, 'f_to')} - /> -

- +
+

{getShortAddress(element.f_to)}

+ handleCopyClick(element, 'f_to')} + /> +

{(element.f_value / 10 ** 18).toLocaleString()} ETH

{(element.f_gas_fee_cap / 10 ** 18).toLocaleString()}

@@ -586,7 +601,7 @@ const BlockPage = () => {
{transactions.map(element => (
{ > Age

-

- {timeSince(element.f_timestamp * 1000)} -

+

{timeSince(element.f_timestamp * 1000)}

-
+

{ > From

+ +

{getShortAddress(element?.f_from)}

+
+

{ > To

-
-
-
-

{getShortAddress(element?.f_from)}

-
- -
-

{getShortAddress(element?.f_to)}

-
+

{getShortAddress(element?.f_to)}

Date: Wed, 22 Nov 2023 13:33:56 +0100 Subject: [PATCH 15/40] add queries for blocks page --- .../components/layouts/BlocksLayout.tsx | 43 +++++++++---------- .../client/pages/[network]/block/[id].tsx | 4 +- packages/client/pages/[network]/blocks.tsx | 26 +++++------ packages/server/controllers/blocks.ts | 36 ++++++++++++++++ packages/server/routes/blocks.ts | 8 +++- 5 files changed, 80 insertions(+), 37 deletions(-) diff --git a/packages/client/components/layouts/BlocksLayout.tsx b/packages/client/components/layouts/BlocksLayout.tsx index fe308558..bb9de57f 100644 --- a/packages/client/components/layouts/BlocksLayout.tsx +++ b/packages/client/components/layouts/BlocksLayout.tsx @@ -3,22 +3,21 @@ import React, { useState, useRef, useContext, useEffect } from 'react'; // Contexts import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; -// Components -import LinkValidator from '../ui/LinkValidator'; -import LinkSlot from '../ui/LinkSlot'; // Types -import { Slot } from '../../types'; +import { BlockEL } from '../../types'; import axiosClient from '../../config/axios'; import { useRouter } from 'next/router'; +import LinkBlock from '../ui/LinkBlock'; +import LinkSlot from '../ui/LinkSlot'; // Props type Props = { - slots: Slot[]; + blocks: BlockEL[]; }; -const Blocks = ({ slots }: Props) => { +const Blocks = ({ blocks }: Props) => { // Theme Mode Context const { themeMode } = useContext(ThemeModeContext) ?? {}; @@ -93,7 +92,7 @@ const Blocks = ({ slots }: Props) => { ))}

- {slots.map(element => ( + {blocks.map(element => (
{ : 'var(--boxShadowCardLight)', color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} - key={element.f_proposer_slot} + key={element.f_slot} >
- +
- +

- {new Date(blockGenesis + Number(element.f_proposer_slot) * 12000).toLocaleString( + {new Date(blockGenesis + Number(element.f_slot) * 12000).toLocaleString( 'ja-JP' )}

-

{(element.withdrawals / 10 ** 9).toLocaleString()}

+

{(element.f_el_transactions ?? 0).toLocaleString()}

))} - {slots.length === 0 && ( + {blocks.length === 0 && (

No blocks

@@ -149,7 +148,7 @@ const Blocks = ({ slots }: Props) => { color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} > - {slots.map(slot => ( + {blocks.map(block => (
{ boxShadow: themeMode?.darkMode ? 'var(--boxShadowCardDark)' : 'var(--boxShadowCardLight)', color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} - key={slot.f_proposer_slot} + key={block.f_slot} >
@@ -169,7 +168,7 @@ const Blocks = ({ slots }: Props) => { > Block number:

- +
@@ -181,7 +180,7 @@ const Blocks = ({ slots }: Props) => { > Slot:

- +
@@ -196,7 +195,7 @@ const Blocks = ({ slots }: Props) => {

{new Date( - blockGenesis + Number(slot.f_proposer_slot) * 12000 + blockGenesis + Number(block.f_slot) * 12000 ).toLocaleDateString('ja-JP', { year: 'numeric', month: 'numeric', @@ -205,7 +204,7 @@ const Blocks = ({ slots }: Props) => {

{new Date( - blockGenesis + Number(slot.f_proposer_slot) * 12000 + blockGenesis + Number(block.f_slot) * 12000 ).toLocaleTimeString('ja-JP', { hour: 'numeric', minute: 'numeric', @@ -224,15 +223,15 @@ const Blocks = ({ slots }: Props) => { > Transactions:

-

{(slot.withdrawals / 10 ** 9).toLocaleString()}

+

{(block.f_el_transactions ?? 0).toLocaleString()}

))} - {slots.length === 0 && ( + {blocks.length === 0 && (
-

No slots

+

No blocks

)}
diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index a24ac586..ec33324c 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -330,8 +330,8 @@ const BlockPage = () => { - - + {/* + */} diff --git a/packages/client/pages/[network]/blocks.tsx b/packages/client/pages/[network]/blocks.tsx index b7c4595b..147e60cd 100644 --- a/packages/client/pages/[network]/blocks.tsx +++ b/packages/client/pages/[network]/blocks.tsx @@ -15,7 +15,7 @@ import Loader from '../../components/ui/Loader'; import ViewMoreButton from '../../components/ui/ViewMoreButton'; // Types -import { Slot } from '../../types'; +import { BlockEL, Slot } from '../../types'; const Blocks = () => { // Theme Mode Context @@ -26,25 +26,25 @@ const Blocks = () => { const { network } = router.query; // States - const [slots, setSlots] = useState([]); + const [blocks, setBlocks] = useState([]); const [currentPage, setCurrentPage] = useState(0); const [loading, setLoading] = useState(true); useEffect(() => { - if (network && slots.length === 0) { - getSlots(0); + if (network && blocks.length === 0) { + getBlocks(0); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [network]); - const getSlots = async (page: number) => { + const getBlocks = async (page: number) => { try { setLoading(true); setCurrentPage(page); - const response = await axiosClient.get(`/api/slots`, { + const response = await axiosClient.get(`/api/blocks`, { params: { network, page, @@ -52,11 +52,13 @@ const Blocks = () => { }, }); - setSlots(prevState => [ + console.log("res",response.data) + + setBlocks(prevState => [ ...prevState, - ...response.data.slots.filter( - (slot: Slot) => - !prevState.find((prevSlot: Slot) => prevSlot.f_proposer_slot === slot.f_proposer_slot) + ...response.data.blocks.filter( + (block: BlockEL) => + !prevState.find((prevBlock: BlockEL) => prevBlock.f_slot === block.f_slot) ), ]); } catch (error) { @@ -102,7 +104,7 @@ const Blocks = () => {
-
{slots.length > 0 && }
+
{blocks.length > 0 && }
{loading && (
@@ -110,7 +112,7 @@ const Blocks = () => {
)} - {slots.length > 0 && getSlots(currentPage + 1)} />} + {blocks.length > 0 && getBlocks(currentPage + 1)} />} ); }; diff --git a/packages/server/controllers/blocks.ts b/packages/server/controllers/blocks.ts index ca48400a..3020ddff 100644 --- a/packages/server/controllers/blocks.ts +++ b/packages/server/controllers/blocks.ts @@ -1,6 +1,42 @@ import { Request, Response } from 'express'; import { pgPools, pgListeners } from '../config/db'; +export const getBlocks = async (req: Request, res: Response) => { + + try { + + const { network, page = 0, limit = 32 } = req.query; + + const pgPool = pgPools[network as string]; + + const skip = Number(page) * Number(limit); + + const [ blocks ] = + await Promise.all([ + pgPool.query(` + SELECT f_timestamp, f_slot, f_epoch + f_el_fee_recp, f_el_gas_limit, f_el_gas_used, + f_el_transactions, f_el_block_hash, f_payload_size_bytes, + f_el_block_number + FROM t_block_metrics + ORDER BY f_el_block_number DESC + OFFSET ${skip} + LIMIT ${Number(limit)} + `) + ]); + + res.json({ + blocks: blocks.rows + }); + + } catch (error) { + console.log(error); + return res.status(500).json({ + msg: 'An error occurred on the server' + }); + } +}; + export const getBlockById = async (req: Request, res: Response) => { try { diff --git a/packages/server/routes/blocks.ts b/packages/server/routes/blocks.ts index ea924049..e6c5c3e0 100644 --- a/packages/server/routes/blocks.ts +++ b/packages/server/routes/blocks.ts @@ -2,12 +2,13 @@ import { Router } from 'express'; import { check, query } from 'express-validator'; import { - getBlockById, getTransactionsByBlock, + getBlockById, getTransactionsByBlock, getBlocks } from '../controllers/blocks'; import { checkFields } from '../middlewares/check-fields'; import { existsNetwork } from '../helpers/network-validator'; + const router = Router(); @@ -23,6 +24,11 @@ router.get('/:id/transactions', [ query('network').custom(existsNetwork), checkFields, ], getTransactionsByBlock); +router.get('/', [ + query('network').not().isEmpty(), + query('network').custom(existsNetwork), + checkFields, +], getBlocks); export default router; \ No newline at end of file From 128ddb3a98a310615c181904f96ba51ff9554bbe Mon Sep 17 00:00:00 2001 From: Josep Chetrit Date: Wed, 22 Nov 2023 15:43:32 +0100 Subject: [PATCH 16/40] fix bug --- packages/client/pages/[network]/slot/[id].tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/pages/[network]/slot/[id].tsx b/packages/client/pages/[network]/slot/[id].tsx index e4925536..176b26de 100644 --- a/packages/client/pages/[network]/slot/[id].tsx +++ b/packages/client/pages/[network]/slot/[id].tsx @@ -21,6 +21,7 @@ import LinkEntity from '../../../components/ui/LinkEntity'; // Types import { Block, Withdrawal } from '../../../types'; import LinkBlock from '../../../components/ui/LinkBlock'; +import EpochAnimation from '../../../components/layouts/EpochAnimation'; type CardProps = { title: string; From 0d6f88adb6d002ebf60ee1b9aa9d3ff896164277 Mon Sep 17 00:00:00 2001 From: Josep Chetrit Date: Wed, 22 Nov 2023 16:48:05 +0100 Subject: [PATCH 17/40] fix bug in block page --- .../client/pages/[network]/block/[id].tsx | 61 ++++--------------- packages/server/controllers/blocks.ts | 2 +- 2 files changed, 14 insertions(+), 49 deletions(-) diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index ec33324c..f0790600 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -387,35 +387,7 @@ const BlockPage = () => { colorLetter='black' content={ <> - Time at which the slot - should have passed - (calculated since genesis) - - } - top='34px' - polygonLeft - /> - -
-
-

Method

- - - - - Time at which the slot - should have passed - (calculated since genesis) + The hash of the transaction } top='34px' @@ -439,13 +411,11 @@ const BlockPage = () => { colorLetter='black' content={ <> - Time at which the slot - should have passed - (calculated since genesis) + How long ago + the transaction passed } top='34px' - polygonLeft />
@@ -467,13 +437,13 @@ const BlockPage = () => { colorLetter='black' content={ <> - Time at which the slot - should have passed - (calculated since genesis) + How much ETH + was sent + in the transaction } top='34px' - polygonLeft + />
@@ -493,13 +463,12 @@ const BlockPage = () => { colorLetter='black' content={ <> - Time at which the slot - should have passed - (calculated since genesis) + The fee + the transaction cost } top='34px' - polygonLeft + polygonRight />
@@ -527,10 +496,6 @@ const BlockPage = () => {

{getShortAddress(element?.f_hash)}

-
-

{element.f_tx_type}

-
-

{timeSince(element.f_timestamp *1000)}

{getShortAddress(element.f_from)}

@@ -543,7 +508,7 @@ const BlockPage = () => {

{getShortAddress(element.f_to)}

{(element.f_value / 10 ** 18).toLocaleString()} ETH

-

{(element.f_gas_fee_cap / 10 ** 18).toLocaleString()}

+

{(element.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

))} @@ -674,7 +639,7 @@ const BlockPage = () => { > Txn Fee

-

{(element.f_gas_fee_cap / 10 ** 18).toLocaleString()}

+

{(element.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

))} @@ -733,7 +698,7 @@ const BlockPage = () => {
)} - {block && !loadingBlock && getInformationView()} + {block?.f_slot && !loadingBlock && getInformationView()} ); }; diff --git a/packages/server/controllers/blocks.ts b/packages/server/controllers/blocks.ts index 3020ddff..2fc0f454 100644 --- a/packages/server/controllers/blocks.ts +++ b/packages/server/controllers/blocks.ts @@ -49,7 +49,7 @@ export const getBlockById = async (req: Request, res: Response) => { const [ block ] = await Promise.all([ pgPool.query(` - SELECT f_timestamp, f_slot, f_epoch + SELECT f_timestamp, f_slot, f_epoch, f_el_fee_recp, f_el_gas_limit, f_el_gas_used, f_el_transactions, f_el_block_hash, f_payload_size_bytes FROM t_block_metrics From ca8089707eb71fdf83637bbe9f4be82474479081 Mon Sep 17 00:00:00 2001 From: Josep Chetrit Date: Sun, 26 Nov 2023 18:44:17 +0100 Subject: [PATCH 18/40] add env variable --- packages/client/.env.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/.env.example b/packages/client/.env.example index 88e1c8fc..b3f662ac 100644 --- a/packages/client/.env.example +++ b/packages/client/.env.example @@ -1,3 +1,4 @@ NEXT_PUBLIC_URL_API= NEXT_PUBLIC_GOOGLE_ANALYTICS= -NEXT_PUBLIC_TAG_MANAGER= \ No newline at end of file +NEXT_PUBLIC_TAG_MANAGER= +API_URL= \ No newline at end of file From c198b8a224dd1c9dfaa460d108a7a339f20c1c00 Mon Sep 17 00:00:00 2001 From: mpujadas Date: Tue, 28 Nov 2023 00:12:57 +0100 Subject: [PATCH 19/40] Add copy buttom --- .../client/pages/[network]/block/[id].tsx | 52 ++++++------------ .../static/images/icons/copied_dark.webp | Bin 0 -> 1078 bytes .../static/images/icons/copied_light.webp | Bin 0 -> 1078 bytes 3 files changed, 18 insertions(+), 34 deletions(-) create mode 100644 packages/client/public/static/images/icons/copied_dark.webp create mode 100644 packages/client/public/static/images/icons/copied_light.webp diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index ac4ad450..b3d25341 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -357,32 +357,18 @@ const BlockPage = () => { }; //CopyAddress - type ElementType = { - f_hash?: string; - f_from?: string; - f_to?: string; - }; + const [copied, setCopied] = useState(null) + useEffect(() => { + if (copied) { + setTimeout(() => {setCopied(null)}, 250) + } - const [copied, setCopied] = useState<{ [key: string]: boolean | undefined }>({}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [copied]); - const handleCopyClick = (element: ElementType, property: 'f_hash' | 'f_from' | 'f_to') => { - const copyText = element[property] || ''; - navigator.clipboard - .writeText(copyText) - .then(() => { - console.log('Copied'); - setCopied(prevState => { - return { ...(prevState ?? {}), [element.f_hash || '']: true }; - }); - setTimeout(() => { - setCopied(prevState => { - return { ...(prevState ?? {}), [element.f_hash || '']: false }; - }); - }, 2000); - }) - .catch(err => { - console.error('Error', err); - }); + const handleCopyClick = async (id : string, text : string) => { + await navigator.clipboard.writeText(text) + setCopied(id as any) }; //Transactions tab - table desktop @@ -521,14 +507,14 @@ const BlockPage = () => {

{getShortAddress(element?.f_hash)}

+ handleCopyClick(element, 'f_hash')} + onClick={() => handleCopyClick(`${element.f_hash}#hash`, element.f_hash)} />
@@ -540,12 +526,11 @@ const BlockPage = () => {

{getShortAddress(element.f_from)}

handleCopyClick(element, 'f_from')} + onClick={() => handleCopyClick(`${element.f_hash}#from`, element.f_from)} />
{

{getShortAddress(element.f_to)}

handleCopyClick(element, 'f_to')} - /> + onClick={() => handleCopyClick(`${element.f_hash}#to`,element.f_to)} + />

{(element.f_value / 10 ** 18).toLocaleString()} ETH

{(element.f_gas_fee_cap / 10 ** 18).toLocaleString()}

diff --git a/packages/client/public/static/images/icons/copied_dark.webp b/packages/client/public/static/images/icons/copied_dark.webp new file mode 100644 index 0000000000000000000000000000000000000000..c3ff6df3a087fa4d9b5a14aef652da302dd3147f GIT binary patch literal 1078 zcmV-61j+kSNk&F41ONb6MM6+kP&iB>1ONapUVs-6hr+0BBuTF2k9J>uNYgYJw{7Hz zoBA*GzPEe%?6{F6Mao+C-%h$$?Qt?>BuP*$!8gLGzsaLK!WvwFi?>$^W|aW-x}iYL z>RF0rmf+uZDA2PGX4b4iJ*#K+ETmxO4Q21y|3A-=0X|;FB>8}QmBWCq2rJTA|NCnQgG;i2qWKdQ~bB&_57D@d4P0ld)p|AnJ_yWhCZ{N zO7Fe*&QL@Nx&O-nEPs+_+rNnZJ8&CGkqXpvL&t+ZNdAL3y|{XI)qAEdKKHIJb{6yg zL9ueyBZRl7D|tWUtX`3XS1W}rX5OAj%I8zV>PuIoL#sr7N}ufC#P&0(a$6hkH-xd< zZnwLH@m4o;3&`~o!<9kzorBrXgtyErqJ4HJvXYClG*oL*geQg8v&E|U%;6mN7PazT zG$muX7L$sBlr-TZQgwIO>2SEkOVPgUv{Kv zVUF}*+QGaa4m(Q@GfNjddS^?|1UQzBk%c&l;Nvks&E&0AQzSeVJXQ>2rTO(?3;rgY&QZSp+FwD497Qawhmv)3evh}XZp!tGQb%!Mpy0Zk&Yjbk^yyN z16V11F;gKH!q&D18ZU;}2{37EpkOn^Ygl4tbUZza|CzJ?u=^4Iq2n#f{b5&*H+&3W zWq{#m8X{bnSQ{8GqGnar%izAkJ4Cv>_!_vcNRF8RNz*C3C!OQ=bdVIcsyJmg+i7ySnHsDSu?fHD|?f;v&Kw^OKk=h1g9n-q@cbF=I4{ z*+)SKIz6y?DAy@uvtp2D^Kc&6oj_JO?H*G1}C=SiZb zrrOzcCuT*F*T~E~#jGo;rSeqN>LNd^mZEE|YoU)lim+L$4geU)COQ=1f#SvDE+IEg z)r`4usthAHPEQ-USAF+{vD0q1JB0E2JyGT^sq$&p;;!;Q5wBmDq5Ye<`gW3Rm3Wf( wvp;Wq%f0*}tG`wx_RNGWqC|cM#f9ZYyhMsyaXKM@7$^Q=%0Aykh6951J literal 0 HcmV?d00001 diff --git a/packages/client/public/static/images/icons/copied_light.webp b/packages/client/public/static/images/icons/copied_light.webp new file mode 100644 index 0000000000000000000000000000000000000000..c3ff6df3a087fa4d9b5a14aef652da302dd3147f GIT binary patch literal 1078 zcmV-61j+kSNk&F41ONb6MM6+kP&iB>1ONapUVs-6hr+0BBuTF2k9J>uNYgYJw{7Hz zoBA*GzPEe%?6{F6Mao+C-%h$$?Qt?>BuP*$!8gLGzsaLK!WvwFi?>$^W|aW-x}iYL z>RF0rmf+uZDA2PGX4b4iJ*#K+ETmxO4Q21y|3A-=0X|;FB>8}QmBWCq2rJTA|NCnQgG;i2qWKdQ~bB&_57D@d4P0ld)p|AnJ_yWhCZ{N zO7Fe*&QL@Nx&O-nEPs+_+rNnZJ8&CGkqXpvL&t+ZNdAL3y|{XI)qAEdKKHIJb{6yg zL9ueyBZRl7D|tWUtX`3XS1W}rX5OAj%I8zV>PuIoL#sr7N}ufC#P&0(a$6hkH-xd< zZnwLH@m4o;3&`~o!<9kzorBrXgtyErqJ4HJvXYClG*oL*geQg8v&E|U%;6mN7PazT zG$muX7L$sBlr-TZQgwIO>2SEkOVPgUv{Kv zVUF}*+QGaa4m(Q@GfNjddS^?|1UQzBk%c&l;Nvks&E&0AQzSeVJXQ>2rTO(?3;rgY&QZSp+FwD497Qawhmv)3evh}XZp!tGQb%!Mpy0Zk&Yjbk^yyN z16V11F;gKH!q&D18ZU;}2{37EpkOn^Ygl4tbUZza|CzJ?u=^4Iq2n#f{b5&*H+&3W zWq{#m8X{bnSQ{8GqGnar%izAkJ4Cv>_!_vcNRF8RNz*C3C!OQ=bdVIcsyJmg+i7ySnHsDSu?fHD|?f;v&Kw^OKk=h1g9n-q@cbF=I4{ z*+)SKIz6y?DAy@uvtp2D^Kc&6oj_JO?H*G1}C=SiZb zrrOzcCuT*F*T~E~#jGo;rSeqN>LNd^mZEE|YoU)lim+L$4geU)COQ=1f#SvDE+IEg z)r`4usthAHPEQ-USAF+{vD0q1JB0E2JyGT^sq$&p;;!;Q5wBmDq5Ye<`gW3Rm3Wf( wvp;Wq%f0*}tG`wx_RNGWqC|cM#f9ZYyhMsyaXKM@7$^Q=%0Aykh6951J literal 0 HcmV?d00001 From ed2dcc81d8fcb1b0c7fd6d7dd0684b6d90f3ced5 Mon Sep 17 00:00:00 2001 From: Marina Terentii <125751323+Artentii@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:01:15 +0100 Subject: [PATCH 20/40] Updated copy icon and table style block page --- .../client/pages/[network]/block/[id].tsx | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index b3d25341..145c7b4c 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -496,25 +496,24 @@ const BlockPage = () => {
) : (
{transactions.map(element => ( -
-
-

{getShortAddress(element?.f_hash)}

+
+
handleCopyClick(`${element.f_hash}#hash`, element.f_hash)}> +

{getShortAddress(element?.f_hash)}

handleCopyClick(`${element.f_hash}#hash`, element.f_hash)} + width={20} + height={20} + />
@@ -522,15 +521,13 @@ const BlockPage = () => {

{timeSince(element.f_timestamp * 1000)}

-
-

{getShortAddress(element.f_from)}

+
handleCopyClick(`${element.f_hash}#from`, element.f_from)}> +

{getShortAddress(element.f_from)}

handleCopyClick(`${element.f_hash}#from`, element.f_from)} + width={20} + height={20} />
{ width={25} height={25} /> -
-

{getShortAddress(element.f_to)}

+
handleCopyClick(`${element.f_hash}#to`,element.f_to)} > +

{getShortAddress(element.f_to)}

handleCopyClick(`${element.f_hash}#to`,element.f_to)} + width={20} + height={20} />

{(element.f_value / 10 ** 18).toLocaleString()} ETH

From a7b420deb4076f2d13d2e22ac7ea2903bfab1278 Mon Sep 17 00:00:00 2001 From: Marina Terentii <125751323+Artentii@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:07:33 +0100 Subject: [PATCH 21/40] Run format --- .../client/pages/[network]/block/[id].tsx | 88 +++++++++++++------ 1 file changed, 62 insertions(+), 26 deletions(-) diff --git a/packages/client/pages/[network]/block/[id].tsx b/packages/client/pages/[network]/block/[id].tsx index 145c7b4c..f567ebd9 100644 --- a/packages/client/pages/[network]/block/[id].tsx +++ b/packages/client/pages/[network]/block/[id].tsx @@ -357,18 +357,20 @@ const BlockPage = () => { }; //CopyAddress - const [copied, setCopied] = useState(null) + const [copied, setCopied] = useState(null); useEffect(() => { if (copied) { - setTimeout(() => {setCopied(null)}, 250) + setTimeout(() => { + setCopied(null); + }, 250); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [copied]); - const handleCopyClick = async (id : string, text : string) => { - await navigator.clipboard.writeText(text) - setCopied(id as any) + const handleCopyClick = async (id: string, text: string) => { + await navigator.clipboard.writeText(text); + setCopied(id as any); }; //Transactions tab - table desktop @@ -495,25 +497,39 @@ const BlockPage = () => {
) : ( -
+
{transactions.map(element => ( -
-
handleCopyClick(`${element.f_hash}#hash`, element.f_hash)}> -

{getShortAddress(element?.f_hash)}

- +
+
handleCopyClick(`${element.f_hash}#hash`, element.f_hash)} + > +

+ {getShortAddress(element?.f_hash)} +

+
@@ -521,10 +537,20 @@ const BlockPage = () => {

{timeSince(element.f_timestamp * 1000)}

-
handleCopyClick(`${element.f_hash}#from`, element.f_from)}> -

{getShortAddress(element.f_from)}

+
handleCopyClick(`${element.f_hash}#from`, element.f_from)} + > +

+ {getShortAddress(element.f_from)} +

{ width={25} height={25} /> -
handleCopyClick(`${element.f_hash}#to`,element.f_to)} > -

{getShortAddress(element.f_to)}

+
handleCopyClick(`${element.f_hash}#to`, element.f_to)} + > +

+ {getShortAddress(element.f_to)} +

+ />

{(element.f_value / 10 ** 18).toLocaleString()} ETH

{(element.f_gas_fee_cap / 10 ** 18).toLocaleString()}

From a1f16f3ecd082c89b3b7c9fb2f67872210fc6e75 Mon Sep 17 00:00:00 2001 From: Iuri Date: Wed, 29 Nov 2023 12:12:29 +0100 Subject: [PATCH 22/40] Create transactions page --- packages/client/components/ui/Menu.tsx | 4 + packages/client/pages/blocks.tsx | 2 - packages/client/pages/transactions.tsx | 329 ++++++++++++++++++++ packages/server/controllers/transactions.ts | 33 ++ packages/server/models/server.ts | 5 +- packages/server/routes/transactions.ts | 19 ++ 6 files changed, 389 insertions(+), 3 deletions(-) create mode 100644 packages/client/pages/transactions.tsx create mode 100644 packages/server/controllers/transactions.ts create mode 100644 packages/server/routes/transactions.ts diff --git a/packages/client/components/ui/Menu.tsx b/packages/client/components/ui/Menu.tsx index 7c4e4981..8d644fb4 100644 --- a/packages/client/components/ui/Menu.tsx +++ b/packages/client/components/ui/Menu.tsx @@ -60,6 +60,10 @@ const Menu = () => { name: 'Blocks', route: '/blocks', }, + { + name: 'Transactions', + route: '/transactions', + }, ], Networks: networks.length > 0 diff --git a/packages/client/pages/blocks.tsx b/packages/client/pages/blocks.tsx index ce540606..b1602d0c 100644 --- a/packages/client/pages/blocks.tsx +++ b/packages/client/pages/blocks.tsx @@ -52,8 +52,6 @@ const Blocks = () => { }, }); - console.log('res', response.data); - setBlocks(prevState => [ ...prevState, ...response.data.blocks.filter( diff --git a/packages/client/pages/transactions.tsx b/packages/client/pages/transactions.tsx new file mode 100644 index 00000000..ac5861f5 --- /dev/null +++ b/packages/client/pages/transactions.tsx @@ -0,0 +1,329 @@ +import React, { useState, useEffect, useContext, useRef } from 'react'; +import Head from 'next/head'; +import { useRouter } from 'next/router'; + +// Axios +import axiosClient from '../config/axios'; + +// Contexts +import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; + +// Components +import Layout from '../components/layouts/Layout'; +import Loader from '../components/ui/Loader'; +import ViewMoreButton from '../components/ui/ViewMoreButton'; + +// Types +import { Transaction } from '../types'; + +const Transactions = () => { + // Theme Mode Context + const { themeMode } = useContext(ThemeModeContext) ?? {}; + + // Router + const router = useRouter(); + const { network } = router.query; + + // Refs + const containerRef = useRef(null); + + // States + const [transactions, setTransactions] = useState([]); + const [currentPage, setCurrentPage] = useState(0); + const [loading, setLoading] = useState(false); + const [desktopView, setDesktopView] = useState(true); + + // UseEffect + useEffect(() => { + if (network && transactions.length === 0) { + getTransactions(0); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [network]); + + useEffect(() => { + setDesktopView(window !== undefined && window.innerWidth > 768); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleMouseMove = (e: any) => { + if (containerRef.current) { + const x = e.pageX; + const limit = 0.15; + + if (x < containerRef.current.clientWidth * limit) { + containerRef.current.scrollLeft -= 10; + } else if (x > containerRef.current.clientWidth * (1 - limit)) { + containerRef.current.scrollLeft += 10; + } + } + }; + + //TRANSACTIONS TABLE + const getTransactions = async (page: number) => { + try { + setLoading(true); + setCurrentPage(page); + + const response = await axiosClient.get('/api/transactions', { + params: { + network, + page, + limit: 1, + }, + }); + + setTransactions(prevState => [ + ...prevState, + ...response.data.transactions.filter( + (transaction: Transaction) => + !prevState.find((prevTransaction: Transaction) => prevTransaction.f_hash === transaction.f_hash) + ), + ]); + } catch (error) { + console.log(error); + } finally { + setLoading(false); + } + }; + + // Get Short Address + const getShortAddress = (address: string | undefined) => { + if (typeof address === 'string') { + return `${address.slice(0, 6)}...${address.slice(address.length - 6, address.length)}`; + } else { + return 'Invalid Address'; + } + }; + + const getAge = (timestamp: number) => { + // Calculate the difference in milliseconds + const difference = Date.now() - new Date(timestamp * 1000).getTime(); + + // Convert to a readable timespan + const seconds = Math.floor((difference / 1000) % 60); + const minutes = Math.floor((difference / (1000 * 60)) % 60); + const hours = Math.floor((difference / (1000 * 60 * 60)) % 24); + + const parts = []; + + if (hours > 0) { + parts.push(`${hours}h`); + } + + parts.push(`${minutes}m`); + + if (hours == 0) { + parts.push(`${seconds}s`); + } + + return parts.join(' '); + }; + + //View table desktop + const getTransactionsDesktop = () => { + return ( +
+
+

Txn Hash

+

Age

+

From

+

To

+

Value

+

Txn Fee

+
+ +
+ {transactions.map((transaction: Transaction) => ( +
+

{getShortAddress(transaction.f_hash)}

+ +

{getAge(transaction.f_timestamp)}

+ +

{getShortAddress(transaction.f_from)}

+ +

{getShortAddress(transaction.f_to)}

+ +

{(transaction.f_value / 10 ** 18).toLocaleString()} ETH

+ +

{(transaction.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

+
+ ))} +
+
+ ); + }; + + //View table mobile + const getTransactionsMobile = () => { + return ( +
+ {transactions.map((transaction: Transaction) => ( +
+
+

+ Txn Hash +

+

{getShortAddress(transaction.f_hash)}

+
+ +
+

+ Age +

+

{transaction.f_timestamp}

+
+ +
+

+ From +

+

{getShortAddress(transaction.f_from)}

+
+ +
+

+ To +

+

{getShortAddress(transaction.f_to)}

+
+ +
+

+ Value +

+

{(transaction.f_value / 10 ** 18).toLocaleString()} ETH

+
+ +
+

+ Txn Fee +

+

{(transaction.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

+
+
+ ))} +
+ ); + }; + + //OVERVIEW PAGE + //Overview Transaction page + return ( + + + Transactions of the Ethereum Beacon Chain - EthSeer.io + + + + + +

+ Ethereum Transactions +

+ +
+

+ Transactions in Ethereum refer to a specific period of time in the Beacon Chain. +

+
+ +
{desktopView ? getTransactionsDesktop() : getTransactionsMobile()}
+ + {loading && ( +
+ +
+ )} + + getTransactions(currentPage + 1)} /> +
+ ); +}; + +export default Transactions; diff --git a/packages/server/controllers/transactions.ts b/packages/server/controllers/transactions.ts new file mode 100644 index 00000000..e5b4ae9b --- /dev/null +++ b/packages/server/controllers/transactions.ts @@ -0,0 +1,33 @@ +import { Request, Response } from 'express'; +import { pgPools } from '../config/db'; + +export const getTransactions = async (req: Request, res: Response) => { + + try { + + const { network, page = 0, limit = 10 } = req.query; + + const pgPool = pgPools[network as string]; + + const skip = Number(page) * Number(limit); + + const transactions = + await pgPool.query(` + SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from + FROM t_transactions + ORDER BY f_timestamp DESC + OFFSET ${skip} + LIMIT ${Number(limit)} + `); + + res.json({ + transactions: transactions.rows + }); + + } catch (error) { + console.log(error); + return res.status(500).json({ + msg: 'An error occurred on the server' + }); + } +}; diff --git a/packages/server/models/server.ts b/packages/server/models/server.ts index 2f89a6bd..609214fa 100644 --- a/packages/server/models/server.ts +++ b/packages/server/models/server.ts @@ -8,6 +8,7 @@ import slotsRoutes from '../routes/slots'; import blocksRoutes from '../routes/blocks'; import validatorsRoutes from '../routes/validators'; import networksRoutes from '../routes/networks'; +import transactionsRoutes from '../routes/transactions'; class Server { @@ -20,7 +21,8 @@ class Server { slots: '/api/slots', blocks: '/api/blocks', validators: '/api/validators', - networks: '/api/networks' + networks: '/api/networks', + transactions: '/api/transactions', }; private callsVerbose: boolean; @@ -68,6 +70,7 @@ class Server { this.app.use(this.paths.blocks, blocksRoutes); this.app.use(this.paths.validators, validatorsRoutes); this.app.use(this.paths.networks, networksRoutes); + this.app.use(this.paths.transactions, transactionsRoutes); } listen() { diff --git a/packages/server/routes/transactions.ts b/packages/server/routes/transactions.ts new file mode 100644 index 00000000..4271a954 --- /dev/null +++ b/packages/server/routes/transactions.ts @@ -0,0 +1,19 @@ +import { Router } from 'express'; +import { query } from 'express-validator'; + +import { + getTransactions +} from '../controllers/transactions'; + +import { checkFields } from '../middlewares/check-fields'; +import { existsNetwork } from '../helpers/network-validator'; + +const router = Router(); + +router.get('/', [ + query('network').not().isEmpty(), + query('network').custom(existsNetwork), + checkFields, +], getTransactions); + +export default router; From b12fb85b0ad954b848deabf864133fdfd2f06adf Mon Sep 17 00:00:00 2001 From: Iuri Date: Thu, 30 Nov 2023 12:54:24 +0100 Subject: [PATCH 23/40] Fix design --- packages/client/pages/transactions.tsx | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/client/pages/transactions.tsx b/packages/client/pages/transactions.tsx index ac5861f5..e4bdfbae 100644 --- a/packages/client/pages/transactions.tsx +++ b/packages/client/pages/transactions.tsx @@ -127,25 +127,25 @@ const Transactions = () => { return (
-

Txn Hash

-

Age

-

From

-

To

-

Value

-

Txn Fee

+

Txn Hash

+

Age

+

From

+

To

+

Value

+

Txn Fee

{ color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} > -

{getShortAddress(transaction.f_hash)}

+

{getShortAddress(transaction.f_hash)}

-

{getAge(transaction.f_timestamp)}

+

{getAge(transaction.f_timestamp)}

-

{getShortAddress(transaction.f_from)}

+

{getShortAddress(transaction.f_from)}

-

{getShortAddress(transaction.f_to)}

+

{getShortAddress(transaction.f_to)}

-

{(transaction.f_value / 10 ** 18).toLocaleString()} ETH

+

{(transaction.f_value / 10 ** 18).toLocaleString()} ETH

-

{(transaction.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

+

{(transaction.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

))}
@@ -220,7 +220,7 @@ const Transactions = () => { > Age

-

{transaction.f_timestamp}

+

{getAge(transaction.f_timestamp)}

@@ -313,7 +313,7 @@ const Transactions = () => {
-
{desktopView ? getTransactionsDesktop() : getTransactionsMobile()}
+ <>{desktopView ? getTransactionsDesktop() : getTransactionsMobile()} {loading && (
From 852b987441c2f5a9761c761b8bd39f298c182a96 Mon Sep 17 00:00:00 2001 From: Iuri Date: Thu, 30 Nov 2023 14:39:08 +0100 Subject: [PATCH 24/40] Refactor: Extract Transaction list to a component --- .../components/layouts/Transactions.tsx | 371 ++++++++++++++++ packages/client/helpers/addressHelper.ts | 3 + packages/client/helpers/timeHelper.ts | 42 ++ packages/client/pages/block/[id].tsx | 411 ++---------------- packages/client/pages/transactions.tsx | 225 +--------- packages/server/controllers/blocks.ts | 2 +- 6 files changed, 452 insertions(+), 602 deletions(-) create mode 100644 packages/client/components/layouts/Transactions.tsx create mode 100644 packages/client/helpers/addressHelper.ts create mode 100644 packages/client/helpers/timeHelper.ts diff --git a/packages/client/components/layouts/Transactions.tsx b/packages/client/components/layouts/Transactions.tsx new file mode 100644 index 00000000..6ac038f2 --- /dev/null +++ b/packages/client/components/layouts/Transactions.tsx @@ -0,0 +1,371 @@ +import React, { useState, useEffect, useContext, useRef } from 'react'; + +// Contexts +import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; + +// Components +import TooltipContainer from '../../components/ui/TooltipContainer'; +import TooltipResponsive from '../../components/ui/TooltipResponsive'; +import CustomImage from '../ui/CustomImage'; +import Loader from '../ui/Loader'; + +// Helpers +import { getShortAddress } from '../../helpers/addressHelper'; +import { getTimeAgo } from '../../helpers/timeHelper'; + +// Types +import { Transaction } from '../../types'; + +// Props +type Props = { + transactions: Transaction[]; + loadingTransactions: boolean; +}; + +const Transactions = ({ transactions, loadingTransactions }: Props) => { + // Theme Mode Context + const { themeMode } = useContext(ThemeModeContext) ?? {}; + + // Refs + const containerRef = useRef(null); + + // States + const [desktopView, setDesktopView] = useState(true); + + useEffect(() => { + setDesktopView(window !== undefined && window.innerWidth > 768); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Function to handle the Mouse Move event + const handleMouseMove = (e: any) => { + if (containerRef.current) { + const x = e.pageX; + const limit = 0.15; + + if (x < containerRef.current.clientWidth * limit) { + containerRef.current.scrollLeft -= 10; + } else if (x > containerRef.current.clientWidth * (1 - limit)) { + containerRef.current.scrollLeft += 10; + } + } + }; + + //Transactions tab - table desktop + const getTransactionsDesktop = () => { + return ( +
+
+
+

Txn Hash

+ + + + The hash of the transaction} + top='34px' + polygonLeft + /> + +
+
+

Age

+ + + + + How long ago + the transaction passed + + } + top='34px' + /> + +
+

From

+
+

To

+
+

Value

+ + + + + How much ETH + was sent + in the transaction + + } + top='34px' + /> + +
+
+

Txn Fee

+ + + + + The fee + the transaction cost + + } + top='34px' + polygonRight + /> + +
+
+ +
+ {transactions.map(element => ( +
+
+

{getShortAddress(element?.f_hash)}

+
+ +

{getTimeAgo(element.f_timestamp * 1000)}

+ +

{getShortAddress(element.f_from)}

+ +
+ +
+ +

{getShortAddress(element.f_to)}

+ +

+ {(element.f_value / 10 ** 18).toLocaleString()} ETH +

+

+ {(element.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI +

+
+ ))} + + {!loadingTransactions && transactions.length === 0 && ( +
+

No transactions

+
+ )} +
+ + {loadingTransactions && ( +
+ +
+ )} +
+ ); + }; + + //Transactions tab - table mobile + const getTransactionsMobile = () => { + return ( +
+
+ {transactions.map(element => ( +
+
+

+ Txn Hash +

+

{getShortAddress(element?.f_hash)}

+
+
+

+ Method +

+

{element.f_tx_type}

+
+
+

+ Age +

+

{getTimeAgo(element.f_timestamp * 1000)}

+
+
+

+ From +

+

+ To +

+
+
+
+

{getShortAddress(element?.f_from)}

+
+ +
+

{getShortAddress(element?.f_to)}

+
+
+
+

+ Value +

+

{(element.f_value / 10 ** 18).toLocaleString()} ETH

+
+
+

+ Txn Fee +

+

{(element.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

+
+
+ ))} + + {!loadingTransactions && transactions.length === 0 && ( +
+

No transactions

+
+ )} +
+ + {loadingTransactions && ( +
+ +
+ )} +
+ ); + }; + + return <>{desktopView ? getTransactionsDesktop() : getTransactionsMobile()}; +}; + +export default Transactions; diff --git a/packages/client/helpers/addressHelper.ts b/packages/client/helpers/addressHelper.ts new file mode 100644 index 00000000..81d0362d --- /dev/null +++ b/packages/client/helpers/addressHelper.ts @@ -0,0 +1,3 @@ +export function getShortAddress(address: string) { + return `${address.slice(0, 6)}...${address.slice(-4)}`; +} diff --git a/packages/client/helpers/timeHelper.ts b/packages/client/helpers/timeHelper.ts new file mode 100644 index 00000000..fd2db676 --- /dev/null +++ b/packages/client/helpers/timeHelper.ts @@ -0,0 +1,42 @@ +export function getTimeAgo(timestamp: number) { + const now = new Date(); + const then = new Date(timestamp); + const diff = now.getTime() - then.getTime(); + + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + // Function to add 's' to the end of a string if the value is greater than 1 + const pluralize = (value: number, unit: string) => `${value} ${unit}${value > 1 ? 's' : ''}`; + + // Function to get the days + const getDays = (value: number) => pluralize(value, 'day'); + + // Function to get the hours + const getHours = (value: number) => pluralize(value, 'hr'); + + // Function to get the minutes + const getMinutes = (value: number) => pluralize(value, 'min'); + + // Function to get the seconds + const getSeconds = (value: number) => pluralize(value, 'sec'); + + if (days > 0) { + const hoursMinusDays = hours - days * 24; + return `${getDays(days)} ${getHours(hoursMinusDays)} ago`; + } + + if (hours > 0) { + const minutesMinusHours = minutes - hours * 60; + return `${getHours(hours)} ${getMinutes(minutesMinusHours)} ago`; + } + + if (minutes > 0) { + const secondsMinusMinutes = seconds - minutes * 60; + return `${getMinutes(minutes)} ${getSeconds(secondsMinusMinutes)} ago`; + } + + return `${getSeconds(seconds)} ago`; +} diff --git a/packages/client/pages/block/[id].tsx b/packages/client/pages/block/[id].tsx index 0fe2a0e3..1e381d79 100644 --- a/packages/client/pages/block/[id].tsx +++ b/packages/client/pages/block/[id].tsx @@ -14,13 +14,11 @@ import TabHeader from '../../components/ui/TabHeader'; import Loader from '../../components/ui/Loader'; import LinkSlot from '../../components/ui/LinkSlot'; import Arrow from '../../components/ui/Arrow'; -import CustomImage from '../../components/ui/CustomImage'; -import TooltipContainer from '../../components/ui/TooltipContainer'; -import TooltipResponsive from '../../components/ui/TooltipResponsive'; import LinkBlock from '../../components/ui/LinkBlock'; +import Transactions from '../../components/layouts/Transactions'; // Types -import { BlockEL, Transaction, Withdrawal } from '../../types'; +import { BlockEL, Transaction } from '../../types'; type CardProps = { title: string; @@ -33,32 +31,30 @@ const Card = ({ title, text, content }: CardProps) => { // Theme Mode Context const { themeMode } = React.useContext(ThemeModeContext) ?? {}; return ( - <> -
-

- {title}: -

-
- {text && ( -

- {text} -

- )} +
+

+ {title}: +

+
+ {text && ( +

+ {text} +

+ )} - {content && <>{content}} -
+ {content && <>{content}}
- +
); }; @@ -73,19 +69,15 @@ const BlockPage = () => { // Refs const slotRef = useRef(0); const existsBlockRef = useRef(true); - const containerRef = useRef(null); // States const [block, setBlock] = useState(null); - const [withdrawals, setWithdrawals] = useState>([]); const [transactions, setTransactions] = useState>([]); const [existsBlock, setExistsBlock] = useState(true); const [countdownText, setCountdownText] = useState(''); const [tabPageIndex, setTabPageIndex] = useState(0); const [loadingBlock, setLoadingBlock] = useState(true); - const [loadingWithdrawals, setLoadingWithdrawals] = useState(true); const [loadingTransactions, setLoadingTransactions] = useState(true); - const [desktopView, setDesktopView] = useState(true); const [blockGenesis, setBlockGenesis] = useState(0); // UseEffect @@ -101,11 +93,6 @@ const BlockPage = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [network, id]); - useEffect(() => { - setDesktopView(window !== undefined && window.innerWidth > 768); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); const shuffle = useCallback(() => { const text: string = getCountdownText(); @@ -262,20 +249,6 @@ const BlockPage = () => { return text; }; - // Get Handle Mouse - const handleMouseMove = (e: any) => { - if (containerRef.current) { - const x = e.pageX; - const limit = 0.15; - - if (x < containerRef.current.clientWidth * limit) { - containerRef.current.scrollLeft -= 10; - } else if (x > containerRef.current.clientWidth * (1 - limit)) { - containerRef.current.scrollLeft += 10; - } - } - }; - //TABLE //TABS const getSelectedTab = () => { @@ -284,7 +257,7 @@ const BlockPage = () => { return getOverview(); case 1: - return desktopView ? getTransactionsDesktop() : getTransactionsMobile(); + return ; } }; //TABS - Overview & withdrawals @@ -294,13 +267,11 @@ const BlockPage = () => {
setTabPageIndex(0)} /> {existsBlock && ( - <> - setTabPageIndex(1)} - /> - + setTabPageIndex(1)} + /> )}
{getSelectedTab()} @@ -336,328 +307,6 @@ const BlockPage = () => { ); }; - const timeSince = (timestamp: number) => { - const now = new Date(); - const then = new Date(timestamp); - const diff = now.getTime() - then.getTime(); - - const minutes = Math.floor(diff / 60000); - const hours = Math.floor(minutes / 60); - const remainingMinutes = minutes % 60; - - if (hours === 0) { - return `${remainingMinutes} mins ago`; - } else { - return `${hours} hrs ${remainingMinutes} mins ago`; - } - }; - - //Transactions tab - table desktop - const getTransactionsDesktop = () => { - return ( -
-
-
-

Txn Hash

- - - - - The hash of the transaction - - } - top='34px' - polygonLeft - /> - -
-
-

Age

- - - - - How long ago - the transaction passed - - } - top='34px' - /> - -
-

From

-

To

-
-

Value

- - - - - How much ETH - was sent - in the transaction - - } - top='34px' - /> - -
-
-

Txn Fee

- - - - - The fee - the transaction cost - - } - top='34px' - polygonRight - /> - -
-
- - {loadingTransactions ? ( -
- -
- ) : ( -
- {transactions.map(element => ( -
-
-

{getShortAddress(element?.f_hash)}

-
- -

{timeSince(element.f_timestamp * 1000)}

- -

{getShortAddress(element.f_from)}

- -

{getShortAddress(element.f_to)}

- -

{(element.f_value / 10 ** 18).toLocaleString()} ETH

-

{(element.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

-
- ))} - - {transactions.length == 0 && ( -
-

No transactions

-
- )} -
- )} -
- ); - }; - - //Transactions tab - table mobile - const getTransactionsMobile = () => { - return ( -
- {loadingTransactions ? ( -
- -
- ) : ( -
- {transactions.map(element => ( -
-
-

- Txn Hash -

-

{getShortAddress(element?.f_hash)}

-
-
-

- Method -

-

{element.f_tx_type}

-
-
-

- Age -

-

- {timeSince(element.f_timestamp * 1000)} -

-
-
-

- From -

-

- To -

-
-
-
-

{getShortAddress(element?.f_from)}

-
- -
-

{getShortAddress(element?.f_to)}

-
-
-
-

- Value -

-

{(element.f_value / 10 ** 18).toLocaleString()} ETH

-
-
-

- Txn Fee -

-

{(element.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

-
-
- ))} - {transactions.length == 0 && ( -
-

No transactions

-
- )} -
- )} -
- ); - }; - //OVERVIEW BLOCK PAGE return ( diff --git a/packages/client/pages/transactions.tsx b/packages/client/pages/transactions.tsx index e4bdfbae..f619c0f7 100644 --- a/packages/client/pages/transactions.tsx +++ b/packages/client/pages/transactions.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useContext, useRef } from 'react'; +import React, { useState, useEffect, useContext } from 'react'; import Head from 'next/head'; import { useRouter } from 'next/router'; @@ -10,8 +10,8 @@ import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; // Components import Layout from '../components/layouts/Layout'; -import Loader from '../components/ui/Loader'; import ViewMoreButton from '../components/ui/ViewMoreButton'; +import TransactionsComponent from '../components/layouts/Transactions'; // Types import { Transaction } from '../types'; @@ -24,14 +24,10 @@ const Transactions = () => { const router = useRouter(); const { network } = router.query; - // Refs - const containerRef = useRef(null); - // States const [transactions, setTransactions] = useState([]); const [currentPage, setCurrentPage] = useState(0); const [loading, setLoading] = useState(false); - const [desktopView, setDesktopView] = useState(true); // UseEffect useEffect(() => { @@ -42,25 +38,6 @@ const Transactions = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [network]); - useEffect(() => { - setDesktopView(window !== undefined && window.innerWidth > 768); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const handleMouseMove = (e: any) => { - if (containerRef.current) { - const x = e.pageX; - const limit = 0.15; - - if (x < containerRef.current.clientWidth * limit) { - containerRef.current.scrollLeft -= 10; - } else if (x > containerRef.current.clientWidth * (1 - limit)) { - containerRef.current.scrollLeft += 10; - } - } - }; - //TRANSACTIONS TABLE const getTransactions = async (page: number) => { try { @@ -89,194 +66,6 @@ const Transactions = () => { } }; - // Get Short Address - const getShortAddress = (address: string | undefined) => { - if (typeof address === 'string') { - return `${address.slice(0, 6)}...${address.slice(address.length - 6, address.length)}`; - } else { - return 'Invalid Address'; - } - }; - - const getAge = (timestamp: number) => { - // Calculate the difference in milliseconds - const difference = Date.now() - new Date(timestamp * 1000).getTime(); - - // Convert to a readable timespan - const seconds = Math.floor((difference / 1000) % 60); - const minutes = Math.floor((difference / (1000 * 60)) % 60); - const hours = Math.floor((difference / (1000 * 60 * 60)) % 24); - - const parts = []; - - if (hours > 0) { - parts.push(`${hours}h`); - } - - parts.push(`${minutes}m`); - - if (hours == 0) { - parts.push(`${seconds}s`); - } - - return parts.join(' '); - }; - - //View table desktop - const getTransactionsDesktop = () => { - return ( -
-
-

Txn Hash

-

Age

-

From

-

To

-

Value

-

Txn Fee

-
- -
- {transactions.map((transaction: Transaction) => ( -
-

{getShortAddress(transaction.f_hash)}

- -

{getAge(transaction.f_timestamp)}

- -

{getShortAddress(transaction.f_from)}

- -

{getShortAddress(transaction.f_to)}

- -

{(transaction.f_value / 10 ** 18).toLocaleString()} ETH

- -

{(transaction.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

-
- ))} -
-
- ); - }; - - //View table mobile - const getTransactionsMobile = () => { - return ( -
- {transactions.map((transaction: Transaction) => ( -
-
-

- Txn Hash -

-

{getShortAddress(transaction.f_hash)}

-
- -
-

- Age -

-

{getAge(transaction.f_timestamp)}

-
- -
-

- From -

-

{getShortAddress(transaction.f_from)}

-
- -
-

- To -

-

{getShortAddress(transaction.f_to)}

-
- -
-

- Value -

-

{(transaction.f_value / 10 ** 18).toLocaleString()} ETH

-
- -
-

- Txn Fee -

-

{(transaction.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

-
-
- ))} -
- ); - }; - - //OVERVIEW PAGE //Overview Transaction page return ( @@ -313,13 +102,9 @@ const Transactions = () => {
- <>{desktopView ? getTransactionsDesktop() : getTransactionsMobile()} - - {loading && ( -
- -
- )} +
+ +
getTransactions(currentPage + 1)} /> diff --git a/packages/server/controllers/blocks.ts b/packages/server/controllers/blocks.ts index 2fc0f454..7f021c1d 100644 --- a/packages/server/controllers/blocks.ts +++ b/packages/server/controllers/blocks.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { pgPools, pgListeners } from '../config/db'; +import { pgPools } from '../config/db'; export const getBlocks = async (req: Request, res: Response) => { From a239d14a934ac9fa60db8aa904a54cf9468d8576 Mon Sep 17 00:00:00 2001 From: Marina Terentii <125751323+Artentii@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:29:13 +0100 Subject: [PATCH 25/40] Added SEO to Transactions page --- packages/client/pages/transactions.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/client/pages/transactions.tsx b/packages/client/pages/transactions.tsx index f619c0f7..0cd2e7fe 100644 --- a/packages/client/pages/transactions.tsx +++ b/packages/client/pages/transactions.tsx @@ -70,12 +70,12 @@ const Transactions = () => { return ( - Transactions of the Ethereum Beacon Chain - EthSeer.io - + Transactions of the Ethereum Chain - EthSeer.io + @@ -98,7 +98,7 @@ const Transactions = () => { color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} > - Transactions in Ethereum refer to a specific period of time in the Beacon Chain. + Transactions are the atomic components that create the state of the Ethereum Virtual Machine.
From 3b513f6844854077abc57338d20179a552cc16da Mon Sep 17 00:00:00 2001 From: mpujadas Date: Fri, 1 Dec 2023 18:25:10 +0100 Subject: [PATCH 26/40] Add features block page --- packages/client/.env.example | 2 +- packages/client/pages/block/[id].tsx | 12 +++++++++--- packages/client/types/index.ts | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/client/.env.example b/packages/client/.env.example index b3f662ac..3208f12e 100644 --- a/packages/client/.env.example +++ b/packages/client/.env.example @@ -1,4 +1,4 @@ NEXT_PUBLIC_URL_API= NEXT_PUBLIC_GOOGLE_ANALYTICS= NEXT_PUBLIC_TAG_MANAGER= -API_URL= \ No newline at end of file +URL_API= \ No newline at end of file diff --git a/packages/client/pages/block/[id].tsx b/packages/client/pages/block/[id].tsx index 0fe2a0e3..5b046806 100644 --- a/packages/client/pages/block/[id].tsx +++ b/packages/client/pages/block/[id].tsx @@ -308,6 +308,12 @@ const BlockPage = () => { ); }; + //%Gas usage / limit + const percentGas = (a: number, b: number) => { + return (a / b) * 100; + }; + + //Overview tab - table const getOverview = () => { return ( @@ -328,9 +334,9 @@ const BlockPage = () => { {/* */} - - - + + +
); diff --git a/packages/client/types/index.ts b/packages/client/types/index.ts index 26eedac2..2d3472f4 100644 --- a/packages/client/types/index.ts +++ b/packages/client/types/index.ts @@ -60,7 +60,7 @@ export type BlockEL = { f_el_gas_limit?: number; f_el_gas_used?: number; f_el_transactions?: number; - f_payload_size_bytes?: number; + f_payload_size_bytes?: string; }; export type Slot = { From 33227f948380a89d68ea52030adab4ca127db615 Mon Sep 17 00:00:00 2001 From: mpujadas Date: Fri, 1 Dec 2023 18:42:42 +0100 Subject: [PATCH 27/40] Ad tx details page --- packages/client/pages/transaction/[hash].tsx | 298 +++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 packages/client/pages/transaction/[hash].tsx diff --git a/packages/client/pages/transaction/[hash].tsx b/packages/client/pages/transaction/[hash].tsx new file mode 100644 index 00000000..97481e8d --- /dev/null +++ b/packages/client/pages/transaction/[hash].tsx @@ -0,0 +1,298 @@ +import React, { useEffect, useState, useRef, useCallback, useContext } from 'react'; +import { useRouter } from 'next/router'; +import Head from 'next/head'; + +// Axios +import axiosClient from '../../config/axios'; + +// Contexts +import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; + +// Components +import Layout from '../../components/layouts/Layout'; +import TabHeader from '../../components/ui/TabHeader'; +import Loader from '../../components/ui/Loader'; +import LinkSlot from '../../components/ui/LinkSlot'; +import Arrow from '../../components/ui/Arrow'; + +import LinkBlock from '../../components/ui/LinkBlock'; + +// Types +import { BlockEL, Transaction, Withdrawal } from '../../types'; + +type CardProps = { + title: string; + text?: string; + content?: React.ReactNode; +}; + +//Card style +const Card = ({ title, text, content }: CardProps) => { + // Theme Mode Context + const { themeMode } = React.useContext(ThemeModeContext) ?? {}; + return ( + <> +
+

+ {title}: +

+
+ {text && ( +

+ {text} +

+ )} + + {content && <>{content}} +
+
+ + ); +}; + +const TransactionPage = () => { + // Theme Mode Context + const { themeMode } = useContext(ThemeModeContext) ?? {}; + + // Next router + const router = useRouter(); + const { network, id } = router.query; + + // Refs + const slotRef = useRef(0); + const existsBlockRef = useRef(true); + + // States + const [block, setBlock] = useState(null); + const [existsBlock, setExistsBlock] = useState(true); + const [countdownText, setCountdownText] = useState(''); + const [tabPageIndex, setTabPageIndex] = useState(0); + const [loadingBlock, setLoadingBlock] = useState(true); + const [desktopView, setDesktopView] = useState(true); + const [blockGenesis, setBlockGenesis] = useState(0); + + // UseEffect + useEffect(() => { + if (id) { + slotRef.current = Number(id); + } + + if (network && ((id && !block) || (block && block.f_slot !== Number(id)))) { + getBlock(); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [network, id]); + useEffect(() => { + setDesktopView(window !== undefined && window.innerWidth > 768); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const shuffle = useCallback(() => { + const text: string = getCountdownText(); + setCountdownText(text); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const intervalID = setInterval(shuffle, 1000); + return () => clearInterval(intervalID); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [shuffle, slotRef.current]); + + // Get blocks + const getBlock = async () => { + try { + setLoadingBlock(true); + + const [response, genesisBlock] = await Promise.all([ + axiosClient.get(`/api/blocks/${id}`, { + params: { + network, + }, + }), + axiosClient.get(`/api/networks/block/genesis`, { + params: { + network, + }, + }), + ]); + + const blockResponse: BlockEL = response.data.block; + setBlock(blockResponse); + setBlockGenesis(genesisBlock.data.block_genesis); + + if (!blockResponse) { + const expectedTimestamp = (genesisBlock.data.block_genesis + Number(id) * 12000) / 1000; + + setBlock({ + f_epoch: Math.floor(Number(id) / 32), + f_slot: Number(id), + f_timestamp: expectedTimestamp, + }); + + setExistsBlock(false); + existsBlockRef.current = false; + + const timeDifference = new Date(expectedTimestamp * 1000).getTime() - new Date().getTime(); + + if (timeDifference > 0) { + setTimeout(() => { + if (Number(id) === slotRef.current) { + getBlock(); + } + }, timeDifference + 2000); + } else if (timeDifference > -10000) { + setTimeout(() => { + if (Number(id) === slotRef.current) { + getBlock(); + } + }, 1000); + } + } else { + setExistsBlock(true); + existsBlockRef.current = true; + } + } catch (error) { + console.log(error); + } finally { + setLoadingBlock(false); + } + }; + + // Get Countdown Text + const getCountdownText = () => { + let text = ''; + + if (!existsBlockRef.current) { + const expectedTimestamp = (blockGenesis + slotRef.current * 12000) / 1000; + const timeDifference = new Date(expectedTimestamp * 1000).getTime() - new Date().getTime(); + + const minutesMiliseconds = 1000 * 60; + const hoursMiliseconds = minutesMiliseconds * 60; + const daysMiliseconds = hoursMiliseconds * 24; + const yearsMiliseconds = daysMiliseconds * 365; + + if (timeDifference > yearsMiliseconds) { + const years = Math.floor(timeDifference / yearsMiliseconds); + text = ` (in ${years} ${years > 1 ? 'years' : 'year'})`; + } else if (timeDifference > daysMiliseconds) { + const days = Math.floor(timeDifference / daysMiliseconds); + text = ` (in ${days} ${days > 1 ? 'days' : 'day'})`; + } else if (timeDifference > hoursMiliseconds) { + const hours = Math.floor(timeDifference / hoursMiliseconds); + text = ` (in ${hours} ${hours > 1 ? 'hours' : 'hour'})`; + } else if (timeDifference > minutesMiliseconds) { + const minutes = Math.floor(timeDifference / minutesMiliseconds); + text = ` (in ${minutes} ${minutes > 1 ? 'minutes' : 'minute'})`; + } else if (timeDifference > 1000) { + const seconds = Math.floor(timeDifference / 1000); + text = ` (in ${seconds} ${seconds > 1 ? 'seconds' : 'second'})`; + } else if (timeDifference < -10000) { + text = ' (data not saved)'; + } else { + text = ' (updating...)'; + } + } + + return text; + }; + + //TABS + const getSelectedTab = () => { + switch (tabPageIndex) { + case 0: + return getOverview(); + } + }; + //TABS - Overview & withdrawals + const getInformationView = () => { + return ( +
+ {getSelectedTab()} +
+ ); + }; + + //Overview tab - table + const getOverview = () => { + return ( +
+ {/* Table */} +
+ + } /> + + + + + + + + + + +
+
+ ); + }; + + //OVERVIEW BLOCK PAGE + return ( + + + + + + {/* Header */} +
+ + + + +

+ Transaction {String('123,456')} +

+ + + + +
+ + {loadingBlock && ( +
+ +
+ )} + + {block?.f_slot && !loadingBlock && getInformationView()} +
+ ); +}; + +export default TransactionPage; From 7a90fbebdec1d590082f8f5e708f740c052b7640 Mon Sep 17 00:00:00 2001 From: Iuri Date: Fri, 1 Dec 2023 19:27:05 +0100 Subject: [PATCH 28/40] Remove Method from Transactions --- packages/client/components/layouts/Transactions.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/client/components/layouts/Transactions.tsx b/packages/client/components/layouts/Transactions.tsx index 6ac038f2..d34c6bf1 100644 --- a/packages/client/components/layouts/Transactions.tsx +++ b/packages/client/components/layouts/Transactions.tsx @@ -259,17 +259,6 @@ const Transactions = ({ transactions, loadingTransactions }: Props) => {

{getShortAddress(element?.f_hash)}

-
-

- Method -

-

{element.f_tx_type}

-

Date: Fri, 1 Dec 2023 19:47:29 +0100 Subject: [PATCH 29/40] Create a hook for large view flag --- .../components/layouts/Transactions.tsx | 17 +++++++---------- packages/client/hooks/useLargeView.ts | 19 +++++++++++++++++++ packages/client/pages/transaction/[hash].tsx | 7 +------ 3 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 packages/client/hooks/useLargeView.ts diff --git a/packages/client/components/layouts/Transactions.tsx b/packages/client/components/layouts/Transactions.tsx index d34c6bf1..f1771cf7 100644 --- a/packages/client/components/layouts/Transactions.tsx +++ b/packages/client/components/layouts/Transactions.tsx @@ -1,8 +1,11 @@ -import React, { useState, useEffect, useContext, useRef } from 'react'; +import React, { useContext, useRef } from 'react'; // Contexts import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; +// Hooks +import useLargeView from '../../hooks/useLargeView'; + // Components import TooltipContainer from '../../components/ui/TooltipContainer'; import TooltipResponsive from '../../components/ui/TooltipResponsive'; @@ -29,14 +32,8 @@ const Transactions = ({ transactions, loadingTransactions }: Props) => { // Refs const containerRef = useRef(null); - // States - const [desktopView, setDesktopView] = useState(true); - - useEffect(() => { - setDesktopView(window !== undefined && window.innerWidth > 768); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + // Large View Hook + const largeView = useLargeView(); // Function to handle the Mouse Move event const handleMouseMove = (e: any) => { @@ -354,7 +351,7 @@ const Transactions = ({ transactions, loadingTransactions }: Props) => { ); }; - return <>{desktopView ? getTransactionsDesktop() : getTransactionsMobile()}; + return <>{largeView ? getTransactionsDesktop() : getTransactionsMobile()}; }; export default Transactions; diff --git a/packages/client/hooks/useLargeView.ts b/packages/client/hooks/useLargeView.ts new file mode 100644 index 00000000..6211c9ce --- /dev/null +++ b/packages/client/hooks/useLargeView.ts @@ -0,0 +1,19 @@ +import { useState, useEffect } from 'react'; + +const useLargeView = () => { + const isWindowAvailable = typeof window !== 'undefined'; + const [largeView, setLargeView] = useState(isWindowAvailable ? window.innerWidth > 768 : true); + + useEffect(() => { + const handleResize = () => { + setLargeView(window.innerWidth > 768); + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + return largeView; +}; + +export default useLargeView; diff --git a/packages/client/pages/transaction/[hash].tsx b/packages/client/pages/transaction/[hash].tsx index 97481e8d..e704927c 100644 --- a/packages/client/pages/transaction/[hash].tsx +++ b/packages/client/pages/transaction/[hash].tsx @@ -220,11 +220,7 @@ const TransactionPage = () => { }; //TABS - Overview & withdrawals const getInformationView = () => { - return ( -

- {getSelectedTab()} -
- ); + return
{getSelectedTab()}
; }; //Overview tab - table @@ -251,7 +247,6 @@ const TransactionPage = () => { -
); From 2230e8940c9d648dcb34e00722b8d22925e009e4 Mon Sep 17 00:00:00 2001 From: Iuri Date: Fri, 1 Dec 2023 20:46:01 +0100 Subject: [PATCH 30/40] Add transaction query and refactor transaction page --- packages/client/helpers/addressHelper.ts | 3 +- packages/client/pages/transaction/[hash].tsx | 270 +++++-------------- packages/client/types/index.ts | 1 + packages/server/controllers/transactions.ts | 29 ++ packages/server/routes/transactions.ts | 9 +- 5 files changed, 108 insertions(+), 204 deletions(-) diff --git a/packages/client/helpers/addressHelper.ts b/packages/client/helpers/addressHelper.ts index 81d0362d..cf02922f 100644 --- a/packages/client/helpers/addressHelper.ts +++ b/packages/client/helpers/addressHelper.ts @@ -1,3 +1,4 @@ -export function getShortAddress(address: string) { +export function getShortAddress(address: string | undefined) { + if (!address) return ''; return `${address.slice(0, 6)}...${address.slice(-4)}`; } diff --git a/packages/client/pages/transaction/[hash].tsx b/packages/client/pages/transaction/[hash].tsx index e704927c..9d55d6cb 100644 --- a/packages/client/pages/transaction/[hash].tsx +++ b/packages/client/pages/transaction/[hash].tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef, useCallback, useContext } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import { useRouter } from 'next/router'; import Head from 'next/head'; @@ -10,15 +10,14 @@ import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; // Components import Layout from '../../components/layouts/Layout'; -import TabHeader from '../../components/ui/TabHeader'; import Loader from '../../components/ui/Loader'; -import LinkSlot from '../../components/ui/LinkSlot'; -import Arrow from '../../components/ui/Arrow'; - import LinkBlock from '../../components/ui/LinkBlock'; +// Helpers +import { getShortAddress } from '../../helpers/addressHelper'; + // Types -import { BlockEL, Transaction, Withdrawal } from '../../types'; +import { Transaction } from '../../types'; type CardProps = { title: string; @@ -31,32 +30,30 @@ const Card = ({ title, text, content }: CardProps) => { // Theme Mode Context const { themeMode } = React.useContext(ThemeModeContext) ?? {}; return ( - <> -
-

- {title}: -

-
- {text && ( -

- {text} -

- )} - - {content && <>{content}} -
+
+

+ {title}: +

+
+ {text && ( +

+ {text} +

+ )} + + {content && <>{content}}
- +
); }; @@ -66,164 +63,44 @@ const TransactionPage = () => { // Next router const router = useRouter(); - const { network, id } = router.query; - - // Refs - const slotRef = useRef(0); - const existsBlockRef = useRef(true); + const { network, hash } = router.query; // States - const [block, setBlock] = useState(null); - const [existsBlock, setExistsBlock] = useState(true); - const [countdownText, setCountdownText] = useState(''); - const [tabPageIndex, setTabPageIndex] = useState(0); - const [loadingBlock, setLoadingBlock] = useState(true); - const [desktopView, setDesktopView] = useState(true); - const [blockGenesis, setBlockGenesis] = useState(0); + const [transaction, setTransaction] = useState(null); + const [loading, setLoading] = useState(true); // UseEffect useEffect(() => { - if (id) { - slotRef.current = Number(id); - } - - if (network && ((id && !block) || (block && block.f_slot !== Number(id)))) { - getBlock(); + if (network && hash && !transaction) { + getTransaction(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [network, id]); - useEffect(() => { - setDesktopView(window !== undefined && window.innerWidth > 768); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const shuffle = useCallback(() => { - const text: string = getCountdownText(); - setCountdownText(text); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - const intervalID = setInterval(shuffle, 1000); - return () => clearInterval(intervalID); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [shuffle, slotRef.current]); + }, [network, hash]); - // Get blocks - const getBlock = async () => { + // Get Transaction + const getTransaction = async () => { try { - setLoadingBlock(true); + setLoading(true); - const [response, genesisBlock] = await Promise.all([ - axiosClient.get(`/api/blocks/${id}`, { - params: { - network, - }, - }), - axiosClient.get(`/api/networks/block/genesis`, { - params: { - network, - }, - }), - ]); + const response = await axiosClient.get(`/api/transactions/${hash}`, { + params: { + network, + }, + }); - const blockResponse: BlockEL = response.data.block; - setBlock(blockResponse); - setBlockGenesis(genesisBlock.data.block_genesis); - - if (!blockResponse) { - const expectedTimestamp = (genesisBlock.data.block_genesis + Number(id) * 12000) / 1000; - - setBlock({ - f_epoch: Math.floor(Number(id) / 32), - f_slot: Number(id), - f_timestamp: expectedTimestamp, - }); - - setExistsBlock(false); - existsBlockRef.current = false; - - const timeDifference = new Date(expectedTimestamp * 1000).getTime() - new Date().getTime(); - - if (timeDifference > 0) { - setTimeout(() => { - if (Number(id) === slotRef.current) { - getBlock(); - } - }, timeDifference + 2000); - } else if (timeDifference > -10000) { - setTimeout(() => { - if (Number(id) === slotRef.current) { - getBlock(); - } - }, 1000); - } - } else { - setExistsBlock(true); - existsBlockRef.current = true; - } + setTransaction(response.data.transaction); } catch (error) { console.log(error); } finally { - setLoadingBlock(false); + setLoading(false); } }; - // Get Countdown Text - const getCountdownText = () => { - let text = ''; - - if (!existsBlockRef.current) { - const expectedTimestamp = (blockGenesis + slotRef.current * 12000) / 1000; - const timeDifference = new Date(expectedTimestamp * 1000).getTime() - new Date().getTime(); - - const minutesMiliseconds = 1000 * 60; - const hoursMiliseconds = minutesMiliseconds * 60; - const daysMiliseconds = hoursMiliseconds * 24; - const yearsMiliseconds = daysMiliseconds * 365; - - if (timeDifference > yearsMiliseconds) { - const years = Math.floor(timeDifference / yearsMiliseconds); - text = ` (in ${years} ${years > 1 ? 'years' : 'year'})`; - } else if (timeDifference > daysMiliseconds) { - const days = Math.floor(timeDifference / daysMiliseconds); - text = ` (in ${days} ${days > 1 ? 'days' : 'day'})`; - } else if (timeDifference > hoursMiliseconds) { - const hours = Math.floor(timeDifference / hoursMiliseconds); - text = ` (in ${hours} ${hours > 1 ? 'hours' : 'hour'})`; - } else if (timeDifference > minutesMiliseconds) { - const minutes = Math.floor(timeDifference / minutesMiliseconds); - text = ` (in ${minutes} ${minutes > 1 ? 'minutes' : 'minute'})`; - } else if (timeDifference > 1000) { - const seconds = Math.floor(timeDifference / 1000); - text = ` (in ${seconds} ${seconds > 1 ? 'seconds' : 'second'})`; - } else if (timeDifference < -10000) { - text = ' (data not saved)'; - } else { - text = ' (updating...)'; - } - } - - return text; - }; - - //TABS - const getSelectedTab = () => { - switch (tabPageIndex) { - case 0: - return getOverview(); - } - }; - //TABS - Overview & withdrawals const getInformationView = () => { - return
{getSelectedTab()}
; + return
{getOverview()}
; }; - //Overview tab - table const getOverview = () => { return (
{ color: themeMode?.darkMode ? 'var(--white)' : 'var(--black)', }} > - {/* Table */}
- - } /> - - - - - - - - - + + } /> + + + + +
); }; - //OVERVIEW BLOCK PAGE return ( - {/* Header */} -
- - - - -

- Transaction {String('123,456')} -

- - - - -
+

+ Transaction +

- {loadingBlock && ( + {loading && (
)} - {block?.f_slot && !loadingBlock && getInformationView()} + {transaction && getInformationView()}
); }; diff --git a/packages/client/types/index.ts b/packages/client/types/index.ts index 26eedac2..753c854b 100644 --- a/packages/client/types/index.ts +++ b/packages/client/types/index.ts @@ -88,6 +88,7 @@ export type Transaction = { f_timestamp: number; f_from: string; f_tx_type: number; + f_el_block_number?: number; }; export type Proposed = { diff --git a/packages/server/controllers/transactions.ts b/packages/server/controllers/transactions.ts index e5b4ae9b..58667a44 100644 --- a/packages/server/controllers/transactions.ts +++ b/packages/server/controllers/transactions.ts @@ -31,3 +31,32 @@ export const getTransactions = async (req: Request, res: Response) => { }); } }; + +export const getTransactionByHash = async (req: Request, res: Response) => { + + try { + + const { hash } = req.params; + + const { network } = req.query; + + const pgPool = pgPools[network as string]; + + const transaction = + await pgPool.query(` + SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from, f_el_block_number + FROM t_transactions + WHERE LOWER(f_hash) = '${hash.toLowerCase()}' + `); + + res.json({ + transaction: transaction.rows[0] + }); + + } catch (error) { + console.log(error); + return res.status(500).json({ + msg: 'An error occurred on the server' + }); + } +}; \ No newline at end of file diff --git a/packages/server/routes/transactions.ts b/packages/server/routes/transactions.ts index 4271a954..fa9b1433 100644 --- a/packages/server/routes/transactions.ts +++ b/packages/server/routes/transactions.ts @@ -2,7 +2,8 @@ import { Router } from 'express'; import { query } from 'express-validator'; import { - getTransactions + getTransactions, + getTransactionByHash, } from '../controllers/transactions'; import { checkFields } from '../middlewares/check-fields'; @@ -16,4 +17,10 @@ router.get('/', [ checkFields, ], getTransactions); +router.get('/:hash', [ + query('network').not().isEmpty(), + query('network').custom(existsNetwork), + checkFields, +], getTransactionByHash); + export default router; From eebd0eb8aebe451d2f2c8fd775d779620a394264 Mon Sep 17 00:00:00 2001 From: Iuri Date: Fri, 1 Dec 2023 20:54:54 +0100 Subject: [PATCH 31/40] Create Link Transaction Component --- .../components/layouts/Transactions.tsx | 5 +- .../client/components/ui/LinkTransaction.tsx | 47 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 packages/client/components/ui/LinkTransaction.tsx diff --git a/packages/client/components/layouts/Transactions.tsx b/packages/client/components/layouts/Transactions.tsx index f1771cf7..4b0bd74c 100644 --- a/packages/client/components/layouts/Transactions.tsx +++ b/packages/client/components/layouts/Transactions.tsx @@ -11,6 +11,7 @@ import TooltipContainer from '../../components/ui/TooltipContainer'; import TooltipResponsive from '../../components/ui/TooltipResponsive'; import CustomImage from '../ui/CustomImage'; import Loader from '../ui/Loader'; +import LinkTransaction from '../ui/LinkTransaction'; // Helpers import { getShortAddress } from '../../helpers/addressHelper'; @@ -176,7 +177,7 @@ const Transactions = ({ transactions, loadingTransactions }: Props) => { key={element.f_hash} >
-

{getShortAddress(element?.f_hash)}

+

{getTimeAgo(element.f_timestamp * 1000)}

@@ -254,7 +255,7 @@ const Transactions = ({ transactions, loadingTransactions }: Props) => { > Txn Hash

-

{getShortAddress(element?.f_hash)}

+

{ + // Theme Mode Context + const { themeMode } = useContext(ThemeModeContext) ?? {}; + + const baseStyle = { + color: themeMode?.darkMode ? 'var(--purple)' : 'var(--darkPurple)', + }; + + return ( + + {children ?? ( + <> + {getShortAddress(hash)} + + + )} + + ); +}; + +export default LinkTransaction; From 584fdb230f07389116787bcbe0fa72da0a639e58 Mon Sep 17 00:00:00 2001 From: Iuri Date: Sat, 2 Dec 2023 10:33:27 +0100 Subject: [PATCH 32/40] Rename Blocks Component --- .../layouts/{BlocksLayout.tsx => Blocks.tsx} | 13 ++++++++----- packages/client/pages/blocks.tsx | 12 +++++------- packages/client/pages/slots.tsx | 6 +++--- 3 files changed, 16 insertions(+), 15 deletions(-) rename packages/client/components/layouts/{BlocksLayout.tsx => Blocks.tsx} (99%) diff --git a/packages/client/components/layouts/BlocksLayout.tsx b/packages/client/components/layouts/Blocks.tsx similarity index 99% rename from packages/client/components/layouts/BlocksLayout.tsx rename to packages/client/components/layouts/Blocks.tsx index 73fca4fd..a92fc16c 100644 --- a/packages/client/components/layouts/BlocksLayout.tsx +++ b/packages/client/components/layouts/Blocks.tsx @@ -1,16 +1,19 @@ import React, { useState, useRef, useContext, useEffect } from 'react'; +import { useRouter } from 'next/router'; + +// Axios +import axiosClient from '../../config/axios'; // Contexts import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; -// Types -import { BlockEL } from '../../types'; - -import axiosClient from '../../config/axios'; -import { useRouter } from 'next/router'; +// Components import LinkBlock from '../ui/LinkBlock'; import LinkSlot from '../ui/LinkSlot'; +// Types +import { BlockEL } from '../../types'; + // Props type Props = { blocks: BlockEL[]; diff --git a/packages/client/pages/blocks.tsx b/packages/client/pages/blocks.tsx index ba5d6c22..4a144471 100644 --- a/packages/client/pages/blocks.tsx +++ b/packages/client/pages/blocks.tsx @@ -2,15 +2,15 @@ import React, { useState, useEffect, useContext } from 'react'; import Head from 'next/head'; import { useRouter } from 'next/router'; -// Contexts -import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; - // Axios import axiosClient from '../config/axios'; +// Contexts +import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; + // Components import Layout from '../components/layouts/Layout'; -import BlocksLayout from '../components/layouts/BlocksLayout'; +import BlockList from '../components/layouts/Blocks'; import Loader from '../components/ui/Loader'; import ViewMoreButton from '../components/ui/ViewMoreButton'; @@ -103,9 +103,7 @@ const Blocks = () => {

-
- {blocks.length > 0 && } -
+
{blocks.length > 0 && }
{loading && (
diff --git a/packages/client/pages/slots.tsx b/packages/client/pages/slots.tsx index 865c3fac..d3dd2785 100644 --- a/packages/client/pages/slots.tsx +++ b/packages/client/pages/slots.tsx @@ -2,12 +2,12 @@ import React, { useState, useEffect, useContext } from 'react'; import Head from 'next/head'; import { useRouter } from 'next/router'; -// Contexts -import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; - // Axios import axiosClient from '../config/axios'; +// Contexts +import ThemeModeContext from '../contexts/theme-mode/ThemeModeContext'; + // Components import Layout from '../components/layouts/Layout'; import SlotsList from '../components/layouts/Slots'; From c37783c80c493890250afbdf25a928384ba11824 Mon Sep 17 00:00:00 2001 From: Iuri Date: Sat, 2 Dec 2023 12:21:00 +0100 Subject: [PATCH 33/40] Fix block page --- packages/client/pages/block/[id].tsx | 62 ---------------------------- 1 file changed, 62 deletions(-) diff --git a/packages/client/pages/block/[id].tsx b/packages/client/pages/block/[id].tsx index 18bcb6a4..90a79059 100644 --- a/packages/client/pages/block/[id].tsx +++ b/packages/client/pages/block/[id].tsx @@ -351,7 +351,6 @@ const BlockPage = () => { return `${hours} hrs ${remainingMinutes} mins ago`; } }; -<<<<<<< HEAD:packages/client/pages/[network]/block/[id].tsx //CopyAddress const [copied, setCopied] = useState(null); @@ -369,8 +368,6 @@ const BlockPage = () => { await navigator.clipboard.writeText(text); setCopied(id as any); }; -======= ->>>>>>> a8feff1f3bbd544207455605cc83597c805108c1:packages/client/pages/block/[id].tsx //Transactions tab - table desktop const getTransactionsDesktop = () => { @@ -398,7 +395,6 @@ const BlockPage = () => { colorLetter='black' content={ <> -<<<<<<< HEAD:packages/client/pages/[network]/block/[id].tsx A transaction hash, often denoted as TXN Hash, serves as a distinctive 66-character identifier produced each time a transaction is executed. @@ -410,36 +406,6 @@ const BlockPage = () => { />
-
-

Method

- - - - - - A function is executed depending on the decoded input data. In cases where - the functions are not recognized, the method ID is presented instead. - -======= - The hash of the transaction ->>>>>>> a8feff1f3bbd544207455605cc83597c805108c1:packages/client/pages/block/[id].tsx - - } - top='34px' - polygonLeft - /> - -

Age

@@ -456,12 +422,7 @@ const BlockPage = () => { colorLetter='black' content={ <> -<<<<<<< HEAD:packages/client/pages/[network]/block/[id].tsx Time has passed since it was created. -======= - How long ago - the transaction passed ->>>>>>> a8feff1f3bbd544207455605cc83597c805108c1:packages/client/pages/block/[id].tsx } top='34px' @@ -472,8 +433,6 @@ const BlockPage = () => {

To

Value

-<<<<<<< HEAD:packages/client/pages/[network]/block/[id].tsx -======= { top='34px' /> ->>>>>>> a8feff1f3bbd544207455605cc83597c805108c1:packages/client/pages/block/[id].tsx

Txn Fee

@@ -514,12 +472,7 @@ const BlockPage = () => { colorLetter='black' content={ <> -<<<<<<< HEAD:packages/client/pages/[network]/block/[id].tsx (Gas price*Gas used by Txns) in Ether -======= - The fee - the transaction cost ->>>>>>> a8feff1f3bbd544207455605cc83597c805108c1:packages/client/pages/block/[id].tsx } top='34px' @@ -536,7 +489,6 @@ const BlockPage = () => { ) : (
{transactions.map(element => ( -<<<<<<< HEAD:packages/client/pages/[network]/block/[id].tsx
{

{element.f_tx_type}

{timeSince(element.f_timestamp * 1000)}

-======= -
-
-

{getShortAddress(element?.f_hash)}

-
- -

{timeSince(element.f_timestamp * 1000)}

->>>>>>> a8feff1f3bbd544207455605cc83597c805108c1:packages/client/pages/block/[id].tsx
{ > Age

-<<<<<<< HEAD:packages/client/pages/[network]/block/[id].tsx

{timeSince(element.f_timestamp * 1000)}

-======= -

- {timeSince(element.f_timestamp * 1000)} -

->>>>>>> a8feff1f3bbd544207455605cc83597c805108c1:packages/client/pages/block/[id].tsx

Date: Mon, 4 Dec 2023 16:52:22 +0100 Subject: [PATCH 34/40] improve queries speed --- packages/client/pages/transactions.tsx | 2 +- packages/server/controllers/transactions.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/client/pages/transactions.tsx b/packages/client/pages/transactions.tsx index 0cd2e7fe..cc12bb61 100644 --- a/packages/client/pages/transactions.tsx +++ b/packages/client/pages/transactions.tsx @@ -48,7 +48,7 @@ const Transactions = () => { params: { network, page, - limit: 1, + limit: 20, }, }); diff --git a/packages/server/controllers/transactions.ts b/packages/server/controllers/transactions.ts index 58667a44..6f7a9c03 100644 --- a/packages/server/controllers/transactions.ts +++ b/packages/server/controllers/transactions.ts @@ -13,9 +13,9 @@ export const getTransactions = async (req: Request, res: Response) => { const transactions = await pgPool.query(` - SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from + SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from, f_el_block_number FROM t_transactions - ORDER BY f_timestamp DESC + ORDER by f_el_block_number desc, f_timestamp desc OFFSET ${skip} LIMIT ${Number(limit)} `); @@ -46,7 +46,7 @@ export const getTransactionByHash = async (req: Request, res: Response) => { await pgPool.query(` SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from, f_el_block_number FROM t_transactions - WHERE LOWER(f_hash) = '${hash.toLowerCase()}' + WHERE f_hash = '${hash}' `); res.json({ From 3d336e55bd645a1d0e40ea1fb5f9ad52af1223e4 Mon Sep 17 00:00:00 2001 From: Iuri Date: Sat, 9 Dec 2023 16:48:22 +0100 Subject: [PATCH 35/40] Add Transaction Hash to the Search Engine --- packages/client/components/ui/SearchEngine.tsx | 8 ++++++++ packages/client/pages/block/[id].tsx | 15 +++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/client/components/ui/SearchEngine.tsx b/packages/client/components/ui/SearchEngine.tsx index 79275556..c34485ef 100644 --- a/packages/client/components/ui/SearchEngine.tsx +++ b/packages/client/components/ui/SearchEngine.tsx @@ -136,6 +136,14 @@ const SearchEngine = () => { }); } + if (searchContent.startsWith('0x')) { + // It can be a transaction + items.push({ + label: `Transaction: ${searchContent}`, + link: `/transaction/${searchContent}`, + }); + } + // It can be an entity const expression = new RegExp(searchContent, 'i'); diff --git a/packages/client/pages/block/[id].tsx b/packages/client/pages/block/[id].tsx index 5ba7d8d7..a0ebcabe 100644 --- a/packages/client/pages/block/[id].tsx +++ b/packages/client/pages/block/[id].tsx @@ -281,9 +281,8 @@ const BlockPage = () => { //%Gas usage / limit const percentGas = (a: number, b: number) => { - return (a / b) * 100; + return (a / b) * 100; }; - //Overview tab - table const getOverview = () => { @@ -305,8 +304,16 @@ const BlockPage = () => { {/* */} - - + +

From c11ef45d0b83f21690c1f39d943c2dbc1761ead9 Mon Sep 17 00:00:00 2001 From: Iuri Date: Sat, 9 Dec 2023 17:36:20 +0100 Subject: [PATCH 36/40] Add more info to transaction page --- packages/client/pages/transaction/[hash].tsx | 64 +++++++++++++++++--- packages/client/types/index.ts | 5 +- packages/server/controllers/transactions.ts | 6 +- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/packages/client/pages/transaction/[hash].tsx b/packages/client/pages/transaction/[hash].tsx index 9d55d6cb..88e10c4e 100644 --- a/packages/client/pages/transaction/[hash].tsx +++ b/packages/client/pages/transaction/[hash].tsx @@ -12,6 +12,7 @@ import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; import Layout from '../../components/layouts/Layout'; import Loader from '../../components/ui/Loader'; import LinkBlock from '../../components/ui/LinkBlock'; +import TabHeader from '../../components/ui/TabHeader'; // Helpers import { getShortAddress } from '../../helpers/addressHelper'; @@ -22,11 +23,12 @@ import { Transaction } from '../../types'; type CardProps = { title: string; text?: string; + wrapText?: boolean; content?: React.ReactNode; }; //Card style -const Card = ({ title, text, content }: CardProps) => { +const Card = ({ title, text, wrapText, content }: CardProps) => { // Theme Mode Context const { themeMode } = React.useContext(ThemeModeContext) ?? {}; return ( @@ -42,7 +44,7 @@ const Card = ({ title, text, content }: CardProps) => {
{text && (

{ // States const [transaction, setTransaction] = useState(null); + const [tabPageIndex, setTabPageIndex] = useState(0); const [loading, setLoading] = useState(true); // UseEffect @@ -97,21 +100,28 @@ const TransactionPage = () => { } }; - const getInformationView = () => { - return

{getOverview()}
; + // Tabs + const getSelectedTab = () => { + switch (tabPageIndex) { + case 0: + return getOverview(); + + case 1: + return getMoreDetails(); + } }; const getOverview = () => { return (
-
+
} /> { title='Transaction Fee' text={`${((transaction?.f_gas_fee_cap ?? 0) / 10 ** 12).toLocaleString()} GWEI`} /> + +
+
+ ); + }; + + const getMoreDetails = () => { + return ( +
+
+ + +
); @@ -151,7 +184,24 @@ const TransactionPage = () => {
)} - {transaction && getInformationView()} + {transaction && ( +
+
+ setTabPageIndex(0)} + /> + setTabPageIndex(1)} + /> +
+ + {getSelectedTab()} +
+ )} ); }; diff --git a/packages/client/types/index.ts b/packages/client/types/index.ts index 7bf140ca..193923eb 100644 --- a/packages/client/types/index.ts +++ b/packages/client/types/index.ts @@ -88,7 +88,10 @@ export type Transaction = { f_timestamp: number; f_from: string; f_tx_type: number; - f_el_block_number?: number; + f_el_block_number: number; + f_gas_price: number; + f_gas: number; + f_data: string; }; export type Proposed = { diff --git a/packages/server/controllers/transactions.ts b/packages/server/controllers/transactions.ts index 6f7a9c03..8862ad64 100644 --- a/packages/server/controllers/transactions.ts +++ b/packages/server/controllers/transactions.ts @@ -13,7 +13,8 @@ export const getTransactions = async (req: Request, res: Response) => { const transactions = await pgPool.query(` - SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from, f_el_block_number + SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from, f_el_block_number, + f_gas_price, f_gas, f_tx_type, f_data FROM t_transactions ORDER by f_el_block_number desc, f_timestamp desc OFFSET ${skip} @@ -44,7 +45,8 @@ export const getTransactionByHash = async (req: Request, res: Response) => { const transaction = await pgPool.query(` - SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from, f_el_block_number + SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from, f_el_block_number, + f_gas_price, f_gas, f_tx_type, f_data FROM t_transactions WHERE f_hash = '${hash}' `); From 93e151c5febf578abb4f96a001fc76f6532a1ee1 Mon Sep 17 00:00:00 2001 From: Iuri Date: Mon, 11 Dec 2023 20:38:51 +0100 Subject: [PATCH 37/40] Add a threshold in the transactions query --- packages/client/pages/transactions.tsx | 5 ++++- packages/client/types/index.ts | 1 + packages/server/controllers/transactions.ts | 5 +++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/client/pages/transactions.tsx b/packages/client/pages/transactions.tsx index cc12bb61..8041691d 100644 --- a/packages/client/pages/transactions.tsx +++ b/packages/client/pages/transactions.tsx @@ -49,6 +49,7 @@ const Transactions = () => { network, page, limit: 20, + threshold: transactions.length > 0 ? transactions[transactions.length - 1].f_tx_idx : null, }, }); @@ -56,7 +57,9 @@ const Transactions = () => { ...prevState, ...response.data.transactions.filter( (transaction: Transaction) => - !prevState.find((prevTransaction: Transaction) => prevTransaction.f_hash === transaction.f_hash) + !prevState.find( + (prevTransaction: Transaction) => prevTransaction.f_tx_idx === transaction.f_tx_idx + ) ), ]); } catch (error) { diff --git a/packages/client/types/index.ts b/packages/client/types/index.ts index 193923eb..82a1545f 100644 --- a/packages/client/types/index.ts +++ b/packages/client/types/index.ts @@ -81,6 +81,7 @@ export type Withdrawal = { }; export type Transaction = { + f_tx_idx: number; f_value: number; f_gas_fee_cap: number; f_to: string; diff --git a/packages/server/controllers/transactions.ts b/packages/server/controllers/transactions.ts index 8862ad64..361ab07f 100644 --- a/packages/server/controllers/transactions.ts +++ b/packages/server/controllers/transactions.ts @@ -5,7 +5,7 @@ export const getTransactions = async (req: Request, res: Response) => { try { - const { network, page = 0, limit = 10 } = req.query; + const { network, page = 0, limit = 10, threshold } = req.query; const pgPool = pgPools[network as string]; @@ -16,7 +16,8 @@ export const getTransactions = async (req: Request, res: Response) => { SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from, f_el_block_number, f_gas_price, f_gas, f_tx_type, f_data FROM t_transactions - ORDER by f_el_block_number desc, f_timestamp desc + ${threshold ? `WHERE f_tx_idx <= ${Number(threshold)}` : ''} + ORDER by f_el_block_number DESC, f_tx_idx DESC, f_timestamp DESC OFFSET ${skip} LIMIT ${Number(limit)} `); From fa3c1311631c1c67e57407dc27dcb8e699cb0f5a Mon Sep 17 00:00:00 2001 From: Iuri Date: Mon, 11 Dec 2023 21:47:56 +0100 Subject: [PATCH 38/40] Fix hidratation issue --- packages/client/components/layouts/Transactions.tsx | 13 ++++++++++++- packages/client/hooks/useLargeView.ts | 5 +++-- packages/client/pages/transactions.tsx | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/client/components/layouts/Transactions.tsx b/packages/client/components/layouts/Transactions.tsx index 4d7a7cb7..19d87091 100644 --- a/packages/client/components/layouts/Transactions.tsx +++ b/packages/client/components/layouts/Transactions.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useRef } from 'react'; +import React, { useContext, useRef, useEffect, useState } from 'react'; // Contexts import ThemeModeContext from '../../contexts/theme-mode/ThemeModeContext'; @@ -37,6 +37,13 @@ const Transactions = ({ transactions, loadingTransactions }: Props) => { // Large View Hook const largeView = useLargeView(); + // States + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + }, []); + // Function to handle the Mouse Move event const handleMouseMove = (e: any) => { if (containerRef.current) { @@ -367,6 +374,10 @@ const Transactions = ({ transactions, loadingTransactions }: Props) => { ); }; + if (!isClient) { + return null; + } + return <>{largeView ? getTransactionsDesktop() : getTransactionsMobile()}; }; diff --git a/packages/client/hooks/useLargeView.ts b/packages/client/hooks/useLargeView.ts index 6211c9ce..ab152ca9 100644 --- a/packages/client/hooks/useLargeView.ts +++ b/packages/client/hooks/useLargeView.ts @@ -1,15 +1,16 @@ import { useState, useEffect } from 'react'; const useLargeView = () => { - const isWindowAvailable = typeof window !== 'undefined'; - const [largeView, setLargeView] = useState(isWindowAvailable ? window.innerWidth > 768 : true); + const [largeView, setLargeView] = useState(true); useEffect(() => { const handleResize = () => { setLargeView(window.innerWidth > 768); }; + handleResize(); window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); }, []); diff --git a/packages/client/pages/transactions.tsx b/packages/client/pages/transactions.tsx index 8041691d..bea0424d 100644 --- a/packages/client/pages/transactions.tsx +++ b/packages/client/pages/transactions.tsx @@ -27,7 +27,7 @@ const Transactions = () => { // States const [transactions, setTransactions] = useState([]); const [currentPage, setCurrentPage] = useState(0); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); // UseEffect useEffect(() => { From 19f05c907ece19237146082ce97770e7a14a92d2 Mon Sep 17 00:00:00 2001 From: Josep Chetrit Date: Mon, 11 Dec 2023 23:02:56 +0100 Subject: [PATCH 39/40] fix small errors and add nonce label --- packages/client/components/layouts/Transactions.tsx | 4 ++-- packages/client/pages/transaction/[hash].tsx | 7 ++++--- packages/client/types/index.ts | 1 + packages/server/controllers/transactions.ts | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/client/components/layouts/Transactions.tsx b/packages/client/components/layouts/Transactions.tsx index 19d87091..d31b7fd8 100644 --- a/packages/client/components/layouts/Transactions.tsx +++ b/packages/client/components/layouts/Transactions.tsx @@ -215,7 +215,7 @@ const Transactions = ({ transactions, loadingTransactions }: Props) => { {(element.f_value / 10 ** 18).toLocaleString()} ETH

- {(element.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI + {(element.f_gas_fee_cap / 10 ** 9).toLocaleString()} GWEI

))} @@ -342,7 +342,7 @@ const Transactions = ({ transactions, loadingTransactions }: Props) => { > Txn Fee

-

{(element.f_gas_fee_cap / 10 ** 12).toLocaleString()} GWEI

+

{(element.f_gas_fee_cap / 10 ** 9).toLocaleString()} GWEI

))} diff --git a/packages/client/pages/transaction/[hash].tsx b/packages/client/pages/transaction/[hash].tsx index 88e10c4e..354bf606 100644 --- a/packages/client/pages/transaction/[hash].tsx +++ b/packages/client/pages/transaction/[hash].tsx @@ -133,11 +133,11 @@ const TransactionPage = () => {
@@ -156,8 +156,9 @@ const TransactionPage = () => { >
+ - +
); diff --git a/packages/client/types/index.ts b/packages/client/types/index.ts index 82a1545f..dfa2e077 100644 --- a/packages/client/types/index.ts +++ b/packages/client/types/index.ts @@ -93,6 +93,7 @@ export type Transaction = { f_gas_price: number; f_gas: number; f_data: string; + f_nonce: number; }; export type Proposed = { diff --git a/packages/server/controllers/transactions.ts b/packages/server/controllers/transactions.ts index 361ab07f..70fb5e06 100644 --- a/packages/server/controllers/transactions.ts +++ b/packages/server/controllers/transactions.ts @@ -47,7 +47,7 @@ export const getTransactionByHash = async (req: Request, res: Response) => { const transaction = await pgPool.query(` SELECT f_tx_idx, f_gas_fee_cap, f_value, f_to, f_hash, f_timestamp, f_from, f_el_block_number, - f_gas_price, f_gas, f_tx_type, f_data + f_gas_price, f_gas, f_tx_type, f_data, f_nonce FROM t_transactions WHERE f_hash = '${hash}' `); From f7ced93903bb30d6618440df01ded765f23af7d2 Mon Sep 17 00:00:00 2001 From: Tarun Date: Tue, 12 Dec 2023 07:36:03 +0100 Subject: [PATCH 40/40] Switch queries and types to num_active_vals --- .../client/components/layouts/Statitstics.tsx | 26 +++++++++---------- packages/client/types/index.ts | 2 +- packages/server/controllers/epochs.ts | 4 +-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/client/components/layouts/Statitstics.tsx b/packages/client/components/layouts/Statitstics.tsx index d2b3d097..39b8366a 100644 --- a/packages/client/components/layouts/Statitstics.tsx +++ b/packages/client/components/layouts/Statitstics.tsx @@ -583,14 +583,14 @@ const Statitstics = ({ showCalculatingEpochs }: Props) => { title='Target' color='#343434' backgroundColor='#f5f5f5' - percent={1 - epoch.f_missing_target / epoch.f_num_vals} + percent={1 - epoch.f_missing_target / epoch.f_num_active_vals} tooltipColor='orange' tooltipContent={ <> Missing Target: {epoch.f_missing_target?.toLocaleString()} - Attestations: {epoch.f_num_vals?.toLocaleString()} + Attestations: {epoch.f_num_active_vals?.toLocaleString()} } widthTooltip={220} @@ -601,14 +601,14 @@ const Statitstics = ({ showCalculatingEpochs }: Props) => { title='Source' color='#343434' backgroundColor='#f5f5f5' - percent={1 - epoch.f_missing_source / epoch.f_num_vals} + percent={1 - epoch.f_missing_source / epoch.f_num_active_vals} tooltipColor='blue' tooltipContent={ <> Missing Source: {epoch.f_missing_source?.toLocaleString()} - Attestations: {epoch.f_num_vals?.toLocaleString()} + Attestations: {epoch.f_num_active_vals?.toLocaleString()} } widthTooltip={220} @@ -619,12 +619,12 @@ const Statitstics = ({ showCalculatingEpochs }: Props) => { title='Head' color='#343434' backgroundColor='#f5f5f5' - percent={1 - epoch.f_missing_head / epoch.f_num_vals} + percent={1 - epoch.f_missing_head / epoch.f_num_active_vals} tooltipColor='purple' tooltipContent={ <> Missing Head: {epoch.f_missing_head?.toLocaleString()} - Attestations: {epoch.f_num_vals?.toLocaleString()} + Attestations: {epoch.f_num_active_vals?.toLocaleString()} } widthTooltip={220} @@ -823,12 +823,12 @@ const Statitstics = ({ showCalculatingEpochs }: Props) => { title='Target' color='#343434' backgroundColor='#f5f5f5' - percent={1 - epoch.f_missing_target / epoch.f_num_vals} + percent={1 - epoch.f_missing_target / epoch.f_num_active_vals} tooltipColor='orange' tooltipContent={ <> Missing Target: {epoch.f_missing_target?.toLocaleString()} - Attestations: {epoch.f_num_vals?.toLocaleString()} + Attestations: {epoch.f_num_active_vals?.toLocaleString()} } widthTooltip={220} @@ -838,12 +838,12 @@ const Statitstics = ({ showCalculatingEpochs }: Props) => { title='Source' color='#343434' backgroundColor='#f5f5f5' - percent={1 - epoch.f_missing_source / epoch.f_num_vals} + percent={1 - epoch.f_missing_source / epoch.f_num_active_vals} tooltipColor='blue' tooltipContent={ <> Missing Source: {epoch.f_missing_source?.toLocaleString()} - Attestations: {epoch.f_num_vals?.toLocaleString()} + Attestations: {epoch.f_num_active_vals?.toLocaleString()} } widthTooltip={220} @@ -853,12 +853,12 @@ const Statitstics = ({ showCalculatingEpochs }: Props) => { title='Head' color='#343434' backgroundColor='#f5f5f5' - percent={1 - epoch.f_missing_head / epoch.f_num_vals} + percent={1 - epoch.f_missing_head / epoch.f_num_active_vals} tooltipColor='purple' tooltipContent={ <> Missing Head: {epoch.f_missing_head?.toLocaleString()} - Attestations: {epoch.f_num_vals?.toLocaleString()} + Attestations: {epoch.f_num_active_vals?.toLocaleString()} } widthTooltip={220} @@ -902,7 +902,7 @@ const Statitstics = ({ showCalculatingEpochs }: Props) => { title='Attesting/Total active' color='#343434' backgroundColor='#f5f5f5' - percent={epoch.f_num_att_vals / epoch.f_num_vals} + percent={epoch.f_num_att_vals / epoch.f_num_active_vals} tooltipColor='bluedark' tooltipContent={ <> diff --git a/packages/client/types/index.ts b/packages/client/types/index.ts index dfa2e077..403586ea 100644 --- a/packages/client/types/index.ts +++ b/packages/client/types/index.ts @@ -2,7 +2,7 @@ export type Epoch = { f_epoch: number; f_slot: number; f_num_att_vals: number; - f_num_vals: number; + f_num_active_vals: number; f_att_effective_balance_eth: number; f_total_effective_balance_eth: number; f_missing_source: number; diff --git a/packages/server/controllers/epochs.ts b/packages/server/controllers/epochs.ts index 712b66f8..754c01cd 100644 --- a/packages/server/controllers/epochs.ts +++ b/packages/server/controllers/epochs.ts @@ -14,7 +14,7 @@ export const getEpochsStatistics = async (req: Request, res: Response) => { const [ epochsStats, blocksStats ] = await Promise.all([ pgPool.query(` - SELECT f_epoch, f_slot, f_num_att_vals, f_num_vals, + SELECT f_epoch, f_slot, f_num_att_vals, f_num_active_vals, f_att_effective_balance_eth, f_total_effective_balance_eth, f_missing_source, f_missing_target, f_missing_head FROM t_epoch_metrics_summary @@ -67,7 +67,7 @@ export const getEpochById = async (req: Request, res: Response) => { const [ epochStats, blocksProposed, withdrawals ] = await Promise.all([ pgPool.query(` - SELECT f_epoch, f_slot, f_num_att_vals, f_num_vals, + SELECT f_epoch, f_slot, f_num_att_vals, f_num_active_vals, f_att_effective_balance_eth, f_total_effective_balance_eth, f_missing_source, f_missing_target, f_missing_head FROM t_epoch_metrics_summary