diff --git a/gatsby-browser.jsx b/gatsby-browser.jsx new file mode 100644 index 000000000..5789734d5 --- /dev/null +++ b/gatsby-browser.jsx @@ -0,0 +1,9 @@ +import React from "react"; +import { Web3Provider } from "./src/context/Web3Context"; +import { SearchProvider } from "./src/context/SearchContext"; + +export const wrapRootElement = ({ element }) => ( + + {element} + +); diff --git a/gatsby-node.js b/gatsby-node.js index 4cb348aed..fb0c0c126 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -65,6 +65,8 @@ exports.sourceNodes = async ({ }, Promise.resolve({})); chains + // Filter out non HTTP(S) RPC URLs + .map((chain) => ({ ...chain, rpc: chain.rpc.filter(rpc => rpc.startsWith('http://') || rpc.startsWith('https://'))})) .filter((chain) => chain.rpc.length > 0) .forEach((chain) => { const icon = chain.icon; diff --git a/gatsby-ssr.jsx b/gatsby-ssr.jsx new file mode 100644 index 000000000..5789734d5 --- /dev/null +++ b/gatsby-ssr.jsx @@ -0,0 +1,9 @@ +import React from "react"; +import { Web3Provider } from "./src/context/Web3Context"; +import { SearchProvider } from "./src/context/SearchContext"; + +export const wrapRootElement = ({ element }) => ( + + {element} + +); diff --git a/src/components/Chain.tsx b/src/components/Chain.tsx index f4ee98a71..1ed2fb8c7 100644 --- a/src/components/Chain.tsx +++ b/src/components/Chain.tsx @@ -12,6 +12,7 @@ import React, { useContext } from "react"; import { Web3Context } from "../context/Web3Context"; import { ChainData } from "../types/chain"; import { ChainIcon } from "./ChainIcon"; +import { Link } from "gatsby"; export const Chain = ({ name, @@ -22,55 +23,65 @@ export const Chain = ({ }: ChainData) => { const { isConnected, handleConnect, handleAddChain } = useContext(Web3Context); - const handleAddChainClick = () => { + + const handleConnectClick = (event: React.MouseEvent) => { + event.preventDefault(); + handleConnect(); + }; + + const handleAddChainClick = (event: React.MouseEvent) => { + event.preventDefault(); handleAddChain({ name, chainId, nativeCurrency, ...rest }); }; + return ( - - - - - - {name} - + + + + + + + {name} + + + + + Chain ID + {chainId} + + + Currency + {nativeCurrency.symbol} + + - - - Chain ID - {chainId} - - - Currency - {nativeCurrency.symbol} - - + {icon && ( + + + + )} - {icon && ( - - - - )} + + {!isConnected ? ( + Connect + ) : ( + Add Chain + )} + - - {!isConnected ? ( - Connect Wallet - ) : ( - Add Chain - )} - - + ); }; diff --git a/src/components/ChainIcon.tsx b/src/components/ChainIcon.tsx index dc85ff24c..3ad80074b 100644 --- a/src/components/ChainIcon.tsx +++ b/src/components/ChainIcon.tsx @@ -3,7 +3,7 @@ import { GatsbyImage } from "gatsby-plugin-image"; import { ChainData } from "../types/chain"; import { Image } from "@chakra-ui/react"; -export const ChainIcon = ({ icon, name }: Pick) => +export const ChainIcon = ({ icon, name, width = "40px" }: Pick & { width?: string; }) => icon.childImageSharp ? ( ) => alt={name} /> ) : ( - + ); diff --git a/src/components/ExternalLink.tsx b/src/components/ExternalLink.tsx new file mode 100644 index 000000000..667b220e1 --- /dev/null +++ b/src/components/ExternalLink.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { ExternalLinkIcon } from "@chakra-ui/icons"; +import { Link } from "@chakra-ui/react"; + +export const ExternalLink = ({ href, children }) => ( + + {children} + +); diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 7573c1610..84a6858d1 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -14,6 +14,7 @@ import { Search } from "./Search"; import { FaGithub } from "react-icons/fa"; import { AddIcon } from "@chakra-ui/icons"; import { Filters } from "./Filters"; +import { Link as GatsbyLink } from "gatsby"; export const Header = ({ showSearch = true, showFilters = true }) => { const { handleConnect, isConnected, address } = useContext(Web3Context); @@ -33,7 +34,9 @@ export const Header = ({ showSearch = true, showFilters = true }) => { width="100%" zIndex="sticky" > - Chainlist + + Chainlist + {showSearch && } {showFilters && } @@ -62,7 +65,7 @@ export const Header = ({ showSearch = true, showFilters = true }) => { size="lg" onClick={handleConnect} > - Connect Wallet + Connect ) : ( diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index f3f23f95f..a24a96743 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -5,7 +5,14 @@ import { Header } from "./Header"; export const Layout = ({ children, headerProps = {} }) => ( - + {children} diff --git a/src/components/RedFlagBadge.tsx b/src/components/RedFlagBadge.tsx new file mode 100644 index 000000000..9dc2bb51d --- /dev/null +++ b/src/components/RedFlagBadge.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { Badge, Tooltip } from "@chakra-ui/react"; +import { ChainData } from "../types/chain"; + +export const RedFlagBadge = ({ redFlags }: Pick) => { + if (!redFlags || redFlags.length === 0) { + return null; + } + const flagLabel = + redFlags[0] === "reusedChainId" + ? "Flagged for reusing chain ID" + : "Flagged for unknown reasons"; + return ( + + + Flagged + + + ); +}; diff --git a/src/components/RpcTable.tsx b/src/components/RpcTable.tsx new file mode 100644 index 000000000..f2b3c09a2 --- /dev/null +++ b/src/components/RpcTable.tsx @@ -0,0 +1,45 @@ +import React, { useContext } from "react"; +import { + Button, + Table, + TableContainer, + Tbody, + Td, + Th, + Thead, + Tr, +} from "@chakra-ui/react"; +import { Web3Context } from "../context/Web3Context"; + +export const RpcTable = ({ rpcs, handleRpcClick }) => { + const { isConnected, handleConnect } = useContext(Web3Context); + + return ( + + + + + RPC URL + + + + + {rpcs.map((rpcUrl) => ( + + {rpcUrl} + + {!isConnected ? ( + Connect + ) : ( + handleRpcClick(rpcUrl)}> + Add Chain + + )} + + + ))} + + + + ); +}; diff --git a/src/components/StatValue.tsx b/src/components/StatValue.tsx new file mode 100644 index 000000000..477e0dd15 --- /dev/null +++ b/src/components/StatValue.tsx @@ -0,0 +1,8 @@ +import React from "react"; +import { VStack } from "@chakra-ui/react"; + +export const StatValue = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); diff --git a/src/components/StatusBadge.tsx b/src/components/StatusBadge.tsx new file mode 100644 index 000000000..02526e805 --- /dev/null +++ b/src/components/StatusBadge.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { Badge } from "@chakra-ui/react"; +import { ChainData } from "../types/chain"; + +export const StatusBadge = ({ status }: Pick) => { + const actualStatus = status ?? "Active"; + const colorScheme = status === "deprecated" ? "yellow" : undefined; + return ( + + {actualStatus} + + ); +}; diff --git a/src/pages/chain/{Chain.chainId}.tsx b/src/pages/chain/{Chain.chainId}.tsx new file mode 100644 index 000000000..bff377bdd --- /dev/null +++ b/src/pages/chain/{Chain.chainId}.tsx @@ -0,0 +1,159 @@ +import React, { useContext } from "react"; +import { + Button, + Flex, + StatGroup, + StatLabel, + Stat, + Heading, + Divider, + Text, +} from "@chakra-ui/react"; +import { graphql } from "gatsby"; +import { Seo } from "../../components/SEO"; +import { Web3Context } from "../../context/Web3Context"; +import { ChainIcon } from "../../components/ChainIcon"; +import { Layout } from "../../components/Layout"; +import { ExternalLink } from "../../components/ExternalLink"; +import { RpcTable } from "../../components/RpcTable"; +import { RedFlagBadge } from "../../components/RedFlagBadge"; +import { StatusBadge } from "../../components/StatusBadge"; +import { StatValue } from "../../components/StatValue"; +import { ChainData } from "../../types/chain"; + +const ChainPage = ({ data }: { data: { chain: ChainData } }) => { + const { + name, + chainId, + nativeCurrency, + icon, + explorers, + rpc, + redFlags, + infoURL, + status, + } = data.chain; + + const { isConnected, handleConnect, handleAddChain } = + useContext(Web3Context); + + const handleAddChainClick = () => { + handleAddChain({ name, chainId, nativeCurrency, rpc, explorers }); + }; + + const handleRpcClick = (rpc: string) => { + handleAddChain({ name, chainId, nativeCurrency, rpc: [rpc], explorers }); + }; + + return ( + <> + + + + + + {icon && ( + + + + )} + {name} + + {!isConnected ? ( + Connect + ) : ( + Add Chain + )} + + + + + Chain ID + + {chainId} + + + + Currency + + {nativeCurrency.symbol} + + + + Status + + + + + + {infoURL && ( + + Info + + {infoURL} + + + )} + {explorers && ( + + Explorers + + {explorers.map((explorer) => ( + + {explorer.url} + + ))} + + + )} + + + + + + > + ); +}; + +export const query = graphql` + query ($id: String) { + chain(id: { eq: $id }) { + id + name + chain + chainId + rpc + icon { + publicURL + childImageSharp { + gatsbyImageData(width: 60, placeholder: NONE) + } + } + nativeCurrency { + decimals + name + symbol + } + explorers { + url + name + standard + } + status + faucets + redFlags + infoURL + } + } +`; + +export default ChainPage; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 051a31179..8ce559e50 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,21 +1,14 @@ -import { graphql, useStaticQuery } from "gatsby"; import React from "react"; import { ChainList } from "../components/ChainList"; import { Seo } from "../components/SEO"; -import { Web3Provider } from "../context/Web3Context"; -import { SearchProvider } from "../context/SearchContext"; import { Layout } from "../components/Layout"; const IndexPage = () => ( <> - - - - - - - + + + > ); diff --git a/src/types/chain.ts b/src/types/chain.ts index 5ca376aac..42100bcfc 100644 --- a/src/types/chain.ts +++ b/src/types/chain.ts @@ -11,6 +11,9 @@ export interface ChainData { chainId: number; nativeCurrency: ChainCurrency; explorers?: BlockExplorer[]; + status?: string; + redFlags?: string[]; + infoURL?: string; } export interface ChainCurrency {