Skip to content

Commit 6a0cb66

Browse files
authored
Verifiers page (#179)
* Add VerifiersTable component and integrate into CCIP directory pages * Update VerifierGrid links to ensure correct navigation for verifiers * Remove debug log from VerifiersTable component * Refactor ChainHero component integration in Verifiers page and remove redundant styles * Add Verifiers.css for styling the Verifiers page layout
1 parent fed6a73 commit 6a0cb66

File tree

8 files changed

+327
-39
lines changed

8 files changed

+327
-39
lines changed

src/components/CCIP/ChainHero/ChainHero.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
.ccip-chain-hero {
22
background: var(--Page-Background-Alt);
33
border-bottom: 1px solid var(--gray-200);
4-
min-height: 241px;
54
}
65

76
.ccip-chain-hero__heading {

src/components/CCIP/ChainHero/ChainHero.tsx

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,13 @@ interface ChainHeroProps {
5454
symbol: string
5555
}
5656
environment: Environment
57+
breadcrumbItems?: Array<{
58+
name: string
59+
url: string
60+
}>
5761
}
5862

59-
function ChainHero({ chains, tokens, network, token, environment, lanes }: ChainHeroProps) {
63+
function ChainHero({ chains, tokens, network, token, environment, lanes, breadcrumbItems }: ChainHeroProps) {
6064
// Get chain-specific tooltip configuration
6165
const chainTooltipConfig = network?.chain ? getChainTooltip(network.chain) : null
6266

@@ -99,47 +103,51 @@ function ChainHero({ chains, tokens, network, token, environment, lanes }: Chain
99103
<div className="ccip-chain-hero__content">
100104
<div className="ccip-chain-hero__top">
101105
<Breadcrumb
102-
items={[
103-
{
104-
name: "CCIP Directory",
105-
url: `/ccip/directory/${environment}`,
106-
},
107-
{
108-
name: network?.name || token?.id || "Current",
109-
url: network
110-
? `/ccip/directory/${environment}/chain/${network.chain}`
111-
: `/ccip/directory/${environment}/token/${token?.id}`,
112-
},
113-
]}
106+
items={
107+
breadcrumbItems || [
108+
{
109+
name: "CCIP Directory",
110+
url: `/ccip/directory/${environment}`,
111+
},
112+
{
113+
name: network?.name || token?.id || "Current",
114+
url: network
115+
? `/ccip/directory/${environment}/chain/${network.chain}`
116+
: `/ccip/directory/${environment}/token/${token?.id}`,
117+
},
118+
]
119+
}
114120
/>
115121
<div className="ccip-chain-hero__chainSearch">
116122
<Search chains={chains} tokens={tokens} small environment={environment} lanes={lanes} />
117123
</div>
118124
</div>
119125

120-
<div className="ccip-chain-hero__heading">
121-
<img
122-
src={network?.logo || token?.logo}
123-
alt=""
124-
className={token?.logo ? "ccip-chain-hero__token-logo" : ""}
125-
onError={({ currentTarget }) => {
126-
currentTarget.onerror = null // prevents looping
127-
currentTarget.src = fallbackTokenIconUrl
128-
}}
129-
/>
130-
<h1>
131-
{network?.name || token?.name}
132-
<span className="ccip-chain-hero__token-logo__symbol">{token?.id}</span>
126+
{(network || token) && (
127+
<div className="ccip-chain-hero__heading">
128+
<img
129+
src={network?.logo || token?.logo}
130+
alt=""
131+
className={token?.logo ? "ccip-chain-hero__token-logo" : ""}
132+
onError={({ currentTarget }) => {
133+
currentTarget.onerror = null // prevents looping
134+
currentTarget.src = fallbackTokenIconUrl
135+
}}
136+
/>
137+
<h1>
138+
{network?.name || token?.name}
139+
<span className="ccip-chain-hero__token-logo__symbol">{token?.id}</span>
133140

134-
{chainTooltipConfig && (
135-
<Tooltip
136-
tip={chainTooltipConfig.content}
137-
hoverable={chainTooltipConfig.hoverable}
138-
hideDelay={chainTooltipConfig.hideDelay}
139-
/>
140-
)}
141-
</h1>
142-
</div>
141+
{chainTooltipConfig && (
142+
<Tooltip
143+
tip={chainTooltipConfig.content}
144+
hoverable={chainTooltipConfig.hoverable}
145+
hideDelay={chainTooltipConfig.hideDelay}
146+
/>
147+
)}
148+
</h1>
149+
</div>
150+
)}
143151
{network && (
144152
<div className="ccip-chain-hero__details">
145153
<div className="ccip-chain-hero__details__item">
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import Address from "~/components/AddressReact.tsx"
2+
import "./Table.css"
3+
import { Environment, Verifier, getVerifierTypeDisplay } from "~/config/data/ccip/index.ts"
4+
import TableSearchInput from "./TableSearchInput.tsx"
5+
import { useState } from "react"
6+
import {
7+
getExplorerAddressUrl,
8+
fallbackVerifierIconUrl,
9+
getChainIcon,
10+
getTitle,
11+
directoryToSupportedChain,
12+
getExplorer,
13+
getChainTypeAndFamily,
14+
} from "~/features/utils/index.ts"
15+
16+
interface VerifiersTableProps {
17+
verifiers: Verifier[]
18+
}
19+
20+
function VerifiersTable({ verifiers }: VerifiersTableProps) {
21+
const [search, setSearch] = useState("")
22+
23+
// Transform verifiers data to include network information
24+
const verifiersWithNetworkInfo = verifiers.map((verifier) => {
25+
const supportedChain = directoryToSupportedChain(verifier.network)
26+
const networkName = getTitle(supportedChain) || verifier.network
27+
const networkLogo = getChainIcon(supportedChain) || ""
28+
const explorer = getExplorer(supportedChain)
29+
const { chainType } = getChainTypeAndFamily(supportedChain)
30+
31+
return {
32+
...verifier,
33+
networkName,
34+
networkLogo,
35+
supportedChain,
36+
explorer,
37+
chainType,
38+
}
39+
})
40+
41+
const filteredVerifiers = verifiersWithNetworkInfo.filter(
42+
(verifier) =>
43+
verifier.name.toLowerCase().includes(search.toLowerCase()) ||
44+
verifier.networkName.toLowerCase().includes(search.toLowerCase()) ||
45+
verifier.address.toLowerCase().includes(search.toLowerCase()) ||
46+
getVerifierTypeDisplay(verifier.type).toLowerCase().includes(search.toLowerCase())
47+
)
48+
49+
return (
50+
<>
51+
<div className="ccip-table__filters">
52+
<div className="ccip-table__filters-title">
53+
Verifiers <span>({verifiers.length})</span>
54+
</div>
55+
<TableSearchInput search={search} setSearch={setSearch} />
56+
</div>
57+
<div className="ccip-table__wrapper">
58+
<table className="ccip-table">
59+
<thead>
60+
<tr>
61+
<th>Verifier</th>
62+
<th>Network</th>
63+
<th>Verifier address</th>
64+
<th>Verifier type</th>
65+
</tr>
66+
</thead>
67+
<tbody>
68+
{filteredVerifiers.map((verifier, index) => (
69+
<tr key={`${verifier.network}-${verifier.address}`}>
70+
<td>
71+
<div className="ccip-table__network-name">
72+
<span className="ccip-table__logoContainer">
73+
<img
74+
src={verifier.logo}
75+
alt={`${verifier.name} verifier logo`}
76+
className="ccip-table__logo"
77+
onError={({ currentTarget }) => {
78+
currentTarget.onerror = null // prevents looping
79+
currentTarget.src = fallbackVerifierIconUrl
80+
}}
81+
/>
82+
</span>
83+
{verifier.name}
84+
</div>
85+
</td>
86+
<td>
87+
<div className="ccip-table__network-name">
88+
<span className="ccip-table__logoContainer">
89+
<img
90+
src={verifier.networkLogo}
91+
alt={`${verifier.networkName} blockchain logo`}
92+
className="ccip-table__logo"
93+
onError={({ currentTarget }) => {
94+
currentTarget.onerror = null // prevents looping
95+
currentTarget.src = fallbackVerifierIconUrl
96+
}}
97+
/>
98+
</span>
99+
{verifier.networkName}
100+
</div>
101+
</td>
102+
<td data-clipboard-type="verifier-address">
103+
<Address
104+
contractUrl={
105+
verifier.explorer
106+
? getExplorerAddressUrl(verifier.explorer, verifier.chainType)(verifier.address)
107+
: ""
108+
}
109+
address={verifier.address}
110+
endLength={4}
111+
/>
112+
</td>
113+
<td>{getVerifierTypeDisplay(verifier.type)}</td>
114+
</tr>
115+
))}
116+
</tbody>
117+
</table>
118+
<div className="ccip-table__notFound">{filteredVerifiers.length === 0 && <>No verifiers found</>}</div>
119+
</div>
120+
</>
121+
)
122+
}
123+
124+
export default VerifiersTable

src/components/CCIP/VerifierGrid/VerifierGrid.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function VerifierGrid({ verifiers, environment }: VerifierGridProps) {
2020
items={verifiers}
2121
initialDisplayCount={BEFORE_SEE_MORE}
2222
seeMoreLabel="View all verifiers"
23-
seeMoreLink="/verifiers"
23+
seeMoreLink={`/ccip/directory/${environment}/verifiers`}
2424
renderItem={(verifier) => {
2525
const subtitle = `${verifier.totalNetworks} ${verifier.totalNetworks === 1 ? "network" : "networks"}`
2626
const logoElement = (
@@ -34,8 +34,8 @@ function VerifierGrid({ verifiers, environment }: VerifierGridProps) {
3434
logo={logoElement}
3535
title={verifier.name}
3636
subtitle={subtitle}
37-
link={`/ccip/directory/${environment}/verifier/${verifier.id}`}
38-
ariaLabel={`View ${verifier.name} verifier details`}
37+
link={`/ccip/directory/${environment}/verifiers`}
38+
ariaLabel={`View verifiers page for ${verifier.name}`}
3939
/>
4040
)
4141
}}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
import CcipDirectoryLayout from "~/layouts/CcipDirectoryLayout.astro"
3+
import { getEntry, render } from "astro:content"
4+
import { getAllNetworks, getAllVerifiers, getSearchLanes, Version, Environment } from "~/config/data/ccip"
5+
import Table from "~/components/CCIP/Tables/VerifiersTable"
6+
import { getAllUniqueVerifiers } from "~/config/data/ccip/data.ts"
7+
import { DOCS_BASE_URL } from "~/utils/structuredData"
8+
import ChainHero from "~/components/CCIP/ChainHero/ChainHero"
9+
import "./Verifiers.css"
10+
11+
interface Props {
12+
environment: Environment
13+
}
14+
15+
const { environment } = Astro.props as Props
16+
17+
const entry = await getEntry("ccip", "index")
18+
if (!entry) {
19+
throw new Error('Could not find "ccip/index" doc. Check src/content/ccip/index.mdx!')
20+
}
21+
22+
const { headings } = await render(entry)
23+
24+
const networks = getAllNetworks({ filter: environment })
25+
26+
const allVerifiers = getAllVerifiers({
27+
environment,
28+
version: Version.V1_2_0,
29+
})
30+
31+
const uniqueVerifiers = getAllUniqueVerifiers({
32+
environment,
33+
version: Version.V1_2_0,
34+
})
35+
36+
const searchLanes = getSearchLanes({ environment })
37+
38+
// Generate dynamic metadata for verifiers page
39+
const environmentText = environment === Environment.Mainnet ? "Mainnet" : "Testnet"
40+
const verifiersMetadata = {
41+
title: `CCIP Verifiers - ${environmentText} Networks`,
42+
description: `View all CCIP verifiers across ${environmentText} networks. Explore ${allVerifiers.length} verifiers, their addresses, types, and supported networks for cross-chain verification.`,
43+
image: "/assets/product-logos/ccip-logo.svg",
44+
excerpt: `CCIP verifiers ${environmentText.toLowerCase()} networks addresses types committee api cross-chain verification blockchain interoperability`,
45+
}
46+
47+
// Generate structured data for verifiers page
48+
const currentPath = new URL(Astro.request.url).pathname
49+
const canonicalForJsonLd = `${DOCS_BASE_URL}${currentPath}`
50+
---
51+
52+
<CcipDirectoryLayout
53+
frontmatter={{
54+
title: verifiersMetadata.title,
55+
section: "ccip",
56+
metadata: {
57+
description: verifiersMetadata.description,
58+
image: verifiersMetadata.image,
59+
excerpt: verifiersMetadata.excerpt,
60+
},
61+
}}
62+
{headings}
63+
environment={environment}
64+
pageTitleOverride={verifiersMetadata.title}
65+
metadataOverride={{
66+
description: verifiersMetadata.description,
67+
image: verifiersMetadata.image,
68+
excerpt: verifiersMetadata.excerpt,
69+
}}
70+
>
71+
<ChainHero
72+
chains={networks}
73+
tokens={uniqueVerifiers.map((verifier) => ({
74+
id: verifier.id,
75+
totalNetworks: verifier.totalNetworks,
76+
logo: verifier.logo,
77+
}))}
78+
lanes={searchLanes}
79+
environment={environment}
80+
breadcrumbItems={[
81+
{
82+
name: "CCIP Directory",
83+
url: `/ccip/directory/${environment}`,
84+
},
85+
{
86+
name: "Verifiers",
87+
url: `/ccip/directory/${environment}/verifiers`,
88+
},
89+
]}
90+
client:load
91+
/>
92+
93+
<section class="layout">
94+
<div>
95+
<Table client:load verifiers={allVerifiers} />
96+
</div>
97+
</section>
98+
</CcipDirectoryLayout>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.layout {
2+
margin: 0 auto;
3+
padding: var(--space-6x);
4+
}
5+
6+
.layout h2 {
7+
color: var(--gray-900);
8+
font-size: 22px;
9+
line-height: var(--space-10x);
10+
padding-bottom: var(--space-4x);
11+
border-bottom: 1px solid var(--gray-200);
12+
margin-bottom: var(--space-6x);
13+
}
14+
15+
.layout h2 span {
16+
color: var(--gray-400);
17+
font-weight: 600;
18+
letter-spacing: 0.5px;
19+
}
20+
21+
.networks__grid {
22+
display: grid;
23+
grid-template-columns: 1fr;
24+
gap: var(--space-8x);
25+
}
26+
27+
.tokens__grid {
28+
display: grid;
29+
grid-template-columns: 1fr 1fr;
30+
gap: var(--space-8x);
31+
}
32+
33+
@media (min-width: 50em) {
34+
.layout {
35+
max-width: 1500px;
36+
}
37+
}
38+
39+
@media (min-width: 992px) {
40+
.layout {
41+
padding: var(--space-10x) var(--space-8x);
42+
}
43+
}

0 commit comments

Comments
 (0)