From afe39150c3cba68c6af43ec9ce9f79f1ce471a50 Mon Sep 17 00:00:00 2001
From: TITI <162849030+0xtiti@users.noreply.github.com>
Date: Wed, 31 Jul 2024 16:19:02 -0300
Subject: [PATCH] feat: chain page details (#9)
closes ZKS-103
closes ZKS-114
closes ZKS-116
closes ZKS-117
closes ZKS-113
Description:
- Add chain page details HTML structure
- Add dashboard chain navigation
FYI: Currently using mock data so all chains show data from chain id:
324. Since ecosystemData mock has all available chains, chain page
change is shown in route but rendered data is just 324 temporary.
---
public/locales/en/common.json | 9 +-
public/locales/es/common.json | 9 +-
src/components/ChainInformation.tsx | 36 +++++++
src/components/FeeParams.tsx | 19 ++++
src/components/InfoBox.tsx | 13 +++
src/components/RPC.tsx | 22 +++++
src/components/TVL.tsx | 20 ++++
src/components/Table.tsx | 8 +-
src/components/index.ts | 5 +
.../ChainDetail/ChainDescription.tsx | 12 +++
src/containers/ChainDetail/ChainMetadata.tsx | 49 ++++++++++
src/containers/ChainDetail/InfoCard.tsx | 16 ++++
src/containers/ChainDetail/index.tsx | 11 +++
src/containers/Header/index.tsx | 5 +-
src/containers/index.ts | 1 +
src/data/chainMockData.json | 93 ++++++++-----------
src/pages/[chain]/index.tsx | 37 +++++---
src/types/Data.ts | 54 ++++++-----
src/utils/format.ts | 4 +
src/utils/misc.ts | 0
20 files changed, 326 insertions(+), 97 deletions(-)
create mode 100644 src/components/ChainInformation.tsx
create mode 100644 src/components/FeeParams.tsx
create mode 100644 src/components/InfoBox.tsx
create mode 100644 src/components/RPC.tsx
create mode 100644 src/components/TVL.tsx
create mode 100644 src/containers/ChainDetail/ChainDescription.tsx
create mode 100644 src/containers/ChainDetail/ChainMetadata.tsx
create mode 100644 src/containers/ChainDetail/InfoCard.tsx
create mode 100644 src/containers/ChainDetail/index.tsx
delete mode 100644 src/utils/misc.ts
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 58bc09c..fd7f356 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -15,7 +15,7 @@
"notFound": "Chain not found"
}
},
- "CHAINPAGE": {
+ "CHAIN": {
"website": "Website",
"explorer": "Explorer",
"launchDate": "Launch date",
@@ -28,10 +28,11 @@
"lastBlockVerified": "Last block verified",
"transactionsPerSecond": "Transactions per second",
"totalBatchesCommitted": "Total batches committed",
+ "totalBatchesExecuted": "Total batches executed",
"totalBatchesVerified": "Total batches verified",
"averageBlockTime": "Average block time"
},
- "ZKCHAINTVL": {
+ "TVL": {
"title": "ZKchain TVL"
},
"RPC": {
@@ -39,10 +40,12 @@
"status": "Status"
},
"FEEPARAMS": {
+ "title": "Fee params",
"batch": "Batch Overhead L1 Gas",
"compute": "Compute Overhead Part",
"maxGasBatch": "Max Gas per Batch"
- }
+ },
+ "backButton": "Go back"
},
"FOOTER": {
"docs": "Documentation",
diff --git a/public/locales/es/common.json b/public/locales/es/common.json
index f85b9a8..bcd8170 100644
--- a/public/locales/es/common.json
+++ b/public/locales/es/common.json
@@ -15,7 +15,7 @@
"notFound": "Cadena no encontrada"
}
},
- "CHAINPAGE": {
+ "CHAIN": {
"website": "Sitio web",
"explorer": "Explorador",
"launchDate": "Fecha de lanzamiento",
@@ -28,10 +28,11 @@
"lastBlockVerified": "Último bloque verificado",
"transactionsPerSecond": "Transacciones por segundo",
"totalBatchesCommitted": "Total de lotes comprometidos",
+ "totalBatchesExecuted": "Total de lotes ejecutados",
"totalBatchesVerified": "Total de lotes verificados",
"averageBlockTime": "Tiempo promedio de bloque"
},
- "ZKCHAINTVL": {
+ "TVL": {
"title": "TVL de ZKchain"
},
"RPC": {
@@ -39,10 +40,12 @@
"status": "Estado"
},
"FEEPARAMS": {
+ "title": "Parámetros de gas",
"batch": "Sobrecarga de lote L1 Gas",
"compute": "Parte de sobrecarga de cómputo",
"maxGasBatch": "Máximo gas por lote"
- }
+ },
+ "backButton": "Volver"
},
"FOOTER": {
"docs": "Documentación",
diff --git a/src/components/ChainInformation.tsx b/src/components/ChainInformation.tsx
new file mode 100644
index 0000000..49bc4d0
--- /dev/null
+++ b/src/components/ChainInformation.tsx
@@ -0,0 +1,36 @@
+import { useTranslation } from 'next-i18next';
+import { InfoBox, Title } from '~/components';
+import { useData } from '~/hooks';
+
+export const ChainInformation = () => {
+ const { t } = useTranslation();
+ const { chainData } = useData();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/FeeParams.tsx b/src/components/FeeParams.tsx
new file mode 100644
index 0000000..51f218b
--- /dev/null
+++ b/src/components/FeeParams.tsx
@@ -0,0 +1,19 @@
+import { useTranslation } from 'next-i18next';
+import { InfoBox, Title } from '~/components';
+import { useData } from '~/hooks';
+
+export const FeeParams = () => {
+ const { t } = useTranslation();
+ const { chainData } = useData();
+
+ return (
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/InfoBox.tsx b/src/components/InfoBox.tsx
new file mode 100644
index 0000000..8a9ead6
--- /dev/null
+++ b/src/components/InfoBox.tsx
@@ -0,0 +1,13 @@
+interface InfoBoxProps {
+ title: string;
+ description: string | number;
+}
+
+export const InfoBox = ({ title, description }: InfoBoxProps) => {
+ return (
+
+
{title}
+
{description}
+
+ );
+};
diff --git a/src/components/RPC.tsx b/src/components/RPC.tsx
new file mode 100644
index 0000000..9a74845
--- /dev/null
+++ b/src/components/RPC.tsx
@@ -0,0 +1,22 @@
+import { useTranslation } from 'next-i18next';
+import { InfoBox, Title } from '~/components';
+import { useData } from '~/hooks';
+
+export const TVL = () => {
+ const { t } = useTranslation();
+ const { chainData } = useData();
+
+ return (
+
+
+
+ {chainData?.metadata?.publicRpcs &&
+ chainData.metadata.publicRpcs.map((rpc, index) => (
+
+
+
+ ))}
+
+
+ );
+};
diff --git a/src/components/TVL.tsx b/src/components/TVL.tsx
new file mode 100644
index 0000000..c841dcc
--- /dev/null
+++ b/src/components/TVL.tsx
@@ -0,0 +1,20 @@
+import { useTranslation } from 'next-i18next';
+import { InfoBox, Title } from '~/components';
+import { useData } from '~/hooks';
+
+export const RPC = () => {
+ const { t } = useTranslation();
+ const { chainData } = useData();
+
+ return (
+
+
+
+ {chainData?.tvl &&
+ Object.entries(chainData.tvl).map(([token, value]) => (
+
+ ))}
+
+
+ );
+};
diff --git a/src/components/Table.tsx b/src/components/Table.tsx
index 4aec5f6..11560e4 100644
--- a/src/components/Table.tsx
+++ b/src/components/Table.tsx
@@ -1,4 +1,5 @@
import { useTranslation } from 'next-i18next';
+import { useRouter } from 'next/router';
import { EcosystemChainData } from '~/types';
import { formatDataNumber } from '~/utils';
@@ -9,6 +10,11 @@ interface TableProps {
export const Table = ({ chains }: TableProps) => {
const { t } = useTranslation();
+ const router = useRouter();
+
+ const handleChainNavigation = (id: number) => {
+ router.push(`/${id}`);
+ };
return (
@@ -22,7 +28,7 @@ export const Table = ({ chains }: TableProps) => {
{chains?.map((data, index) => {
return (
-
+
handleChainNavigation(data.id)}>
{data.name} |
{data.id} |
{data.nativeToken} |
diff --git a/src/components/index.ts b/src/components/index.ts
index 25f07aa..bd43e0e 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -6,5 +6,10 @@ export * from './SearchBar';
export * from './TotalValueLocked';
export * from './Title';
export * from './TitleBanner';
+export * from './InfoBox';
+export * from './FeeParams';
+export * from './RPC';
+export * from './TVL';
+export * from './ChainInformation';
export * from './BasicSelect';
export * from './NotFound';
diff --git a/src/containers/ChainDetail/ChainDescription.tsx b/src/containers/ChainDetail/ChainDescription.tsx
new file mode 100644
index 0000000..b1b4df5
--- /dev/null
+++ b/src/containers/ChainDetail/ChainDescription.tsx
@@ -0,0 +1,12 @@
+import { ChainInformation, FeeParams, RPC, TVL } from '~/components';
+
+export const ChainDescription = () => {
+ return (
+
+ );
+};
diff --git a/src/containers/ChainDetail/ChainMetadata.tsx b/src/containers/ChainDetail/ChainMetadata.tsx
new file mode 100644
index 0000000..35f1f91
--- /dev/null
+++ b/src/containers/ChainDetail/ChainMetadata.tsx
@@ -0,0 +1,49 @@
+import { useTranslation } from 'next-i18next';
+import { useRouter } from 'next/router';
+import { InfoBox } from '~/components';
+import { useData } from '~/hooks';
+import { formatTimestampToDate } from '~/utils';
+
+export const ChainMetadata = () => {
+ const { t } = useTranslation();
+ const { chainData, ecosystemData } = useData();
+ const router = useRouter();
+ const data = chainData?.metadata;
+
+ const handleChange = (event: React.ChangeEvent) => {
+ const selectedChainId = event.target.value;
+ router.push(`/${selectedChainId}`);
+ };
+
+ const handleBack = () => {
+ router.back();
+ };
+
+ return (
+
+
+
+ {/*
*/}
+
+
{data?.chainId}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/containers/ChainDetail/InfoCard.tsx b/src/containers/ChainDetail/InfoCard.tsx
new file mode 100644
index 0000000..1a455cf
--- /dev/null
+++ b/src/containers/ChainDetail/InfoCard.tsx
@@ -0,0 +1,16 @@
+import { InfoBox } from '~/components';
+
+interface InfoCardProps {
+ title: string;
+}
+
+export const InfoCard = ({ title }: InfoCardProps) => {
+ return (
+
+ );
+};
diff --git a/src/containers/ChainDetail/index.tsx b/src/containers/ChainDetail/index.tsx
new file mode 100644
index 0000000..4ec3935
--- /dev/null
+++ b/src/containers/ChainDetail/index.tsx
@@ -0,0 +1,11 @@
+import { ChainMetadata } from './ChainMetadata';
+import { ChainDescription } from './ChainDescription';
+
+export const ChainDetail = () => {
+ return (
+
+
+
+
+ );
+};
diff --git a/src/containers/Header/index.tsx b/src/containers/Header/index.tsx
index 6519891..8a76ce0 100644
--- a/src/containers/Header/index.tsx
+++ b/src/containers/Header/index.tsx
@@ -2,6 +2,7 @@ import { styled } from '@mui/material/styles';
import { IconButton } from '@mui/material';
import LightModeIcon from '@mui/icons-material/LightMode';
import DarkModeIcon from '@mui/icons-material/DarkMode';
+import Link from 'next/link';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
@@ -30,7 +31,9 @@ export const Header = () => {
return (
- ZKchainHub
+
+ ZKchainHub
+
{theme === 'dark' ? : }
diff --git a/src/containers/index.ts b/src/containers/index.ts
index ac2a1af..0698472 100644
--- a/src/containers/index.ts
+++ b/src/containers/index.ts
@@ -4,3 +4,4 @@ export * from './Layout';
export * from './Landing';
export * from './Dashboard';
export * from './LockedAssets';
+export * from './ChainDetail';
diff --git a/src/data/chainMockData.json b/src/data/chainMockData.json
index 767de09..5b90d97 100644
--- a/src/data/chainMockData.json
+++ b/src/data/chainMockData.json
@@ -1,60 +1,49 @@
-[
- {
- "name": "ZKSync Era",
- "chainId": 324,
- "website": "https://example.com",
- "explorer": "https://example.com",
- "launchDate": "2023-12-05",
- "environment": "Production",
- "nativeToken": "ETH",
- "chainType": "ZKRollup",
- "lastBlock": 123456789,
- "lastBlockVerified": 123456788,
- "transactionsPerSecond": 15,
- "totalBatchesCommitted": 1234567890,
- "totalBatchesExecuted": 1234567890,
- "totalBatchesVerified": 123456788,
- "averageBlockTime": 100000,
- "tvl": {
- "ETH": {
- "value": 500000000,
- "address": "0x79db...d692"
- },
- "USDT": {
- "value": 100000000,
- "address": "0x79db...d692"
- },
- "USDC": {
- "value": 50000000,
- "address": "0x79db...d692"
- },
- "WBTC": {
- "value": 30000000,
- "address": "0x79db...d692"
- }
- },
- "rpcs": [
- {
- "status": "Active",
- "url": "https://lrpc.com"
- },
+{
+ "chainType": "Rollup",
+ "tvl": {
+ "ETH": 1000000,
+ "USDC": 500000
+ },
+ "batchesInfo": {
+ "commited": 100,
+ "verified": 90,
+ "proved": 80
+ },
+ "feeParams": {
+ "batchOverheadL1Gas": 50000,
+ "maxPubdataPerBatch": 120000,
+ "maxL2GasPerBatch": 10000000,
+ "priorityTxMaxPubdata": 15000,
+ "minimalL2GasPrice": 0.25
+ },
+ "metadata": {
+ "iconUrl": "https://s2.coinmarketcap.com/static/img/coins/64x64/24091.png",
+ "chainName": "ZKsyncERA",
+ "chainId": "324",
+ "publicRpcs": [
{
- "status": "Active",
- "url": "https://blastapi.com"
+ "url": "https://mainnet.era.zksync.io",
+ "status": true
},
{
- "status": "Inactive",
- "url": "https://llamarpc.com"
+ "url": "https://1rpc.io/zksync2-era",
+ "status": true
},
{
- "status": "Active",
- "url": "https://alchemy.com"
+ "url": "https://zksync.drpc.org",
+ "status": false
}
],
- "feeParams": {
- "batchOverheadL1Gas": 1234567890,
- "computeOverheadPart": 1234567890,
- "maxGasPerBatch": 123456788
- }
+ "websiteUrl": "https://zksync.io/",
+ "explorerUrl": "https://explorer.zksync.io/",
+ "launchDate": 1679626800,
+ "environment": "mainnet",
+ "nativeToken": "ETH"
+ },
+ "l2ChainInfo": {
+ "tps": 10000000,
+ "avgBlockTime": 12,
+ "lastBlock": 1000000,
+ "lastBlockVerified": 999999
}
-]
+}
diff --git a/src/pages/[chain]/index.tsx b/src/pages/[chain]/index.tsx
index 34eed58..c5b142c 100644
--- a/src/pages/[chain]/index.tsx
+++ b/src/pages/[chain]/index.tsx
@@ -1,27 +1,32 @@
import { useEffect } from 'react';
-import { GetStaticPaths, GetStaticProps } from 'next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+import { GetStaticProps, GetStaticPaths, GetStaticPropsContext, InferGetStaticPropsType } from 'next';
import { EcosystemChainData } from '~/types';
import { CustomHead } from '~/components';
import { useData } from '~/hooks';
import { fetchEcosystemData } from '~/utils';
+import { ChainDetail } from '~/containers';
+import { getConfig } from '~/config';
+
+const { DEFAULT_LANG, SUPPORTED_LANGUAGES } = getConfig();
interface ChainProps {
chain: EcosystemChainData;
}
-const Chain = ({ chain }: ChainProps) => {
- const { setSelectedChainId } = useData();
+const Chain = ({ chain }: InferGetStaticPropsType) => {
+ const { setSelectedChainId, refetchChainData } = useData();
useEffect(() => {
- setSelectedChainId(chain.id);
- }, [chain.id, setSelectedChainId]);
+ setSelectedChainId(chain?.id);
+ refetchChainData({ throwOnError: true, cancelRefetch: false });
+ }, [chain?.id, setSelectedChainId, refetchChainData]);
return (
<>
-
- {chain.name}
- {/* TODO: Add chain page containers */}
+
+
>
);
};
@@ -30,14 +35,17 @@ export const getStaticPaths: GetStaticPaths = async () => {
const ecosystemData = await fetchEcosystemData();
const chains = ecosystemData.chains;
- const paths = chains.map((chain: EcosystemChainData) => ({
- params: { chain: chain.id.toString() },
- }));
+ const paths = SUPPORTED_LANGUAGES.flatMap((locale) =>
+ chains.map((chain: EcosystemChainData) => ({
+ params: { chain: chain.id.toString() },
+ locale,
+ })),
+ );
- return { paths, fallback: false };
+ return { paths, fallback: true };
};
-export const getStaticProps: GetStaticProps = async ({ params }) => {
+export const getStaticProps: GetStaticProps = async ({ params, locale }: GetStaticPropsContext) => {
const ecosystemData = await fetchEcosystemData();
const chains = ecosystemData.chains;
const chainId = parseInt(params?.chain as string);
@@ -47,9 +55,12 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
return { notFound: true };
}
+ const i18Config = await serverSideTranslations(locale || DEFAULT_LANG, ['common'], null, SUPPORTED_LANGUAGES);
+
return {
props: {
chain,
+ ...i18Config,
},
};
};
diff --git a/src/types/Data.ts b/src/types/Data.ts
index edce979..21121c0 100644
--- a/src/types/Data.ts
+++ b/src/types/Data.ts
@@ -1,33 +1,39 @@
export interface ChainData {
- name: string;
- chainId: number;
- website: string;
- explorer: string;
- launchDate: string;
- environment: string;
- nativeToken: string;
chainType: string;
- lastBlock: number;
- lastBlockVerified: number;
- transactionsPerSecond: number;
- totalBatchesCommitted: number;
- totalBatchesExecuted: number;
- totalBatchesVerified: number;
- averageBlockTime: number;
tvl: {
- [token: string]: {
- value: number;
- address: string;
- };
+ [token: string]: number;
+ };
+ batchesInfo: {
+ commited: number;
+ verified: number;
+ proved: number;
};
- rpcs: {
- status: string;
- url: string;
- }[];
feeParams: {
batchOverheadL1Gas: number;
- computeOverheadPart: number;
- maxGasPerBatch: number;
+ maxPubdataPerBatch: number;
+ maxL2GasPerBatch: number;
+ priorityTxMaxPubdata: number;
+ minimalL2GasPrice: number;
+ };
+ metadata: {
+ iconUrl: string;
+ chainName: string;
+ chainId: number;
+ publicRpcs: {
+ url: string;
+ status: boolean;
+ }[];
+ websiteUrl: string;
+ explorerUrl: string;
+ launchDate: number;
+ environment: string;
+ nativeToken: string;
+ };
+ l2ChainInfo: {
+ tps: number;
+ avgBlockTime: number;
+ lastBlock: number;
+ lastBlockVerified: number;
};
}
diff --git a/src/utils/format.ts b/src/utils/format.ts
index 1b39c3d..542020c 100644
--- a/src/utils/format.ts
+++ b/src/utils/format.ts
@@ -2,6 +2,10 @@ export const truncateAddress = (address: string) => {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
};
+export const formatTimestampToDate = (timestamp: number): string => {
+ return new Date(timestamp * 1000).toLocaleDateString();
+};
+
export function formatDataNumber(input: string | number, formatDecimal = 3, currency?: boolean, compact?: boolean) {
const res: number = Number.parseFloat(input.toString());
diff --git a/src/utils/misc.ts b/src/utils/misc.ts
deleted file mode 100644
index e69de29..0000000