From 8cdf93c7b5ca4b85cb2b98691557f033c2b6ad29 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 30 Sep 2024 13:06:32 -0400 Subject: [PATCH 01/14] Branding updates --- .eslintrc | 10 ++- package.json | 10 +-- public/images/background.svg | 1 + src/components/icons/Chevron.tsx | 6 +- src/components/layout/AppLayout.tsx | 6 +- src/components/layout/BackgroundBanner.tsx | 26 -------- src/components/layout/Card.tsx | 6 +- src/components/layout/Modal.tsx | 10 +-- src/components/nav/Footer.tsx | 14 ++-- src/components/nav/Header.tsx | 16 ++--- src/components/nav/InfoBanner.tsx | 2 +- src/components/search/MiniSearchBar.tsx | 2 +- src/components/search/SearchBar.tsx | 2 +- .../messages/cards/TransactionCard.tsx | 2 +- src/images/logos/hyperlane-explorer.svg | 2 +- src/images/logos/hyperlane-name.svg | 2 +- src/pages/_app.tsx | 32 +++++---- src/pages/_document.tsx | 3 +- src/styles/Color.ts | 41 +++++------- src/styles/fonts.css | 46 ------------- src/styles/fonts.ts | 8 +++ tailwind.config.js | 5 +- yarn.lock | 66 +++++++++---------- 23 files changed, 127 insertions(+), 191 deletions(-) create mode 100644 public/images/background.svg delete mode 100644 src/components/layout/BackgroundBanner.tsx delete mode 100644 src/styles/fonts.css create mode 100644 src/styles/fonts.ts diff --git a/.eslintrc b/.eslintrc index 8d05f63..9edd5f1 100644 --- a/.eslintrc +++ b/.eslintrc @@ -30,6 +30,14 @@ "@typescript-eslint/no-floating-promises": ["error"], "@typescript-eslint/no-non-null-assertion": ["off"], "@typescript-eslint/no-require-imports": ["warn"], - "jsx-a11y/alt-text": ["off"] + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ], + "jsx-a11y/alt-text": ["off"], } } diff --git a/package.json b/package.json index 673ed65..5103f9a 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "@hyperlane-xyz/explorer", "description": "An interchain explorer for the Hyperlane protocol and network.", - "version": "3.13.0", + "version": "5.3.0", "author": "J M Rossy", "dependencies": { "@headlessui/react": "^1.7.17", - "@hyperlane-xyz/registry": "4.3.6", - "@hyperlane-xyz/sdk": "5.2.1", - "@hyperlane-xyz/utils": "5.2.1", - "@hyperlane-xyz/widgets": "5.2.1", + "@hyperlane-xyz/registry": "4.4.1", + "@hyperlane-xyz/sdk": "5.3.0", + "@hyperlane-xyz/utils": "5.3.0", + "@hyperlane-xyz/widgets": "5.3.0", "@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6", "@tanstack/react-query": "^5.35.5", "bignumber.js": "^9.1.2", diff --git a/public/images/background.svg b/public/images/background.svg new file mode 100644 index 0000000..1d4a803 --- /dev/null +++ b/public/images/background.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/icons/Chevron.tsx b/src/components/icons/Chevron.tsx index b34fdf6..2cf37bf 100644 --- a/src/components/icons/Chevron.tsx +++ b/src/components/icons/Chevron.tsx @@ -40,7 +40,7 @@ function _ChevronIcon({ width, height, direction, color, classes }: Props) { ); @@ -124,7 +124,7 @@ function _HyperlaneWideChevron({ width, height, direction, color, classes }: Pro width={width} height={height} className={`${directionClass} ${classes}`} - fill={color || Color.Blue} + fill={color || Color.blue} > diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx index 58c082e..d4c29e8 100644 --- a/src/components/layout/AppLayout.tsx +++ b/src/components/layout/AppLayout.tsx @@ -43,10 +43,10 @@ function getHeadTitle(pathName: string) { const styles = { container: { - backgroundImage: 'url(/images/lines-bg-top.svg)', - backgroundSize: '94vw', + backgroundImage: 'url(/images/background.svg)', + backgroundSize: 'cover', backgroundRepeat: 'no-repeat', - backgroundPosition: 'center 80px', + backgroundPosition: 'center', }, main: { width: 'min(900px,96vw)', diff --git a/src/components/layout/BackgroundBanner.tsx b/src/components/layout/BackgroundBanner.tsx deleted file mode 100644 index 91a0cbb..0000000 --- a/src/components/layout/BackgroundBanner.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { WideChevron } from '@hyperlane-xyz/widgets'; - -import { useStore } from '../../store'; -import { classNameToColor } from '../../styles/Color'; - -export function BackgroundBanner() { - const bannerClassName = useStore((s) => s.bannerClassName); - const colorClass = bannerClassName || 'bg-blue-500'; - - return ( -
- - -
- ); -} - -function Chevron({ color, pos }: { color: string; pos: string }) { - return ( -
- -
- ); -} diff --git a/src/components/layout/Card.tsx b/src/components/layout/Card.tsx index c50b6a4..8870062 100644 --- a/src/components/layout/Card.tsx +++ b/src/components/layout/Card.tsx @@ -7,10 +7,6 @@ interface Props { export function Card({ className, padding = 'p-4 sm:p-5', children }: PropsWithChildren) { return ( -
- {children} -
+
{children}
); } diff --git a/src/components/layout/Modal.tsx b/src/components/layout/Modal.tsx index a5158a5..57d97e5 100644 --- a/src/components/layout/Modal.tsx +++ b/src/components/layout/Modal.tsx @@ -10,7 +10,7 @@ export function Modal({ close, maxWidth, children, -}: PropsWithChildren<{ isOpen: boolean; title: string; close: () => void; maxWidth?: string }>) { +}: PropsWithChildren<{ isOpen: boolean; title?: string; close: () => void; maxWidth?: string }>) { return ( @@ -42,9 +42,11 @@ export function Modal({ maxWidth || 'max-w-xs' } max-h-[90vh] transform overflow-auto rounded-xl bg-white px-4 py-4 text-left shadow-lg transition-all`} > - - {title} - + {!!title && ( + + {title} + + )} {children}
}, { title: 'Discord', url: links.discord, external: true, icon: }, { title: 'Github', url: links.github, external: true, icon: }, - { title: 'Blog', url: links.blog, external: true, icon: }, ]; export function Footer() { @@ -51,12 +49,12 @@ export function Footer() {
-
- +
+
-
Go Interchain
-
With Hyperlane
+
Go interchain
+
with Hyperlane
{/*
@@ -102,7 +100,7 @@ export function Footer() { target={item.external ? '_blank' : '_self'} href={item.url} > - {item?.icon &&
{item?.icon}
} + {item?.icon &&
{item?.icon}
}
{item.title}
@@ -116,6 +114,6 @@ export function Footer() { } const styles = { - linkCol: 'flex flex-col gap-3', + linkCol: 'flex flex-col gap-2', linkItem: 'flex items-center capitalize text-decoration-none hover:underline underline-offset-2', }; diff --git a/src/components/nav/Header.tsx b/src/components/nav/Header.tsx index 136a080..0e3c86b 100644 --- a/src/components/nav/Header.tsx +++ b/src/components/nav/Header.tsx @@ -35,7 +35,7 @@ export function Header({ pathName }: { pathName: string }) { return (
@@ -46,9 +46,9 @@ export function Header({ pathName }: { pathName: string }) { animateHeader && 'scale-90' } transition-all ease-in-out duration-500`} > - - Hyperlane - Explorer + + Hyperlane + Explorer
@@ -169,7 +169,7 @@ function MobileNavLink({ const styles = { navLink: - 'flex items-center font-medium text-white tracking-wide hover:underline active:opacity-80 decoration-4 decoration-pink-500 underline-offset-[2px] transition-all', + 'flex items-center font-medium text-white tracking-wide hover:underline active:opacity-80 decoration-4 decoration-pink-500 underline-offset-[3px] transition-all', dropdownOption: 'flex items-center cursor-pointer p-2 mt-1 rounded text-blue-500 font-medium hover:underline decoration-2 underline-offset-4 transition-all', }; diff --git a/src/components/nav/InfoBanner.tsx b/src/components/nav/InfoBanner.tsx index e6733f9..cd959f2 100644 --- a/src/components/nav/InfoBanner.tsx +++ b/src/components/nav/InfoBanner.tsx @@ -4,7 +4,7 @@ export function InfoBanner() { href="https://explorer-v2.hyperlane.xyz" target="_blank" rel="noopener noreferrer" - className="block py-1.5 w-full text-white text-center text-sm bg-blue-600 hover:bg-blue-700 active:bg-blue-800 ring-1 ring-inset ring-green-700 transition-all duration-300" + className="block py-1.5 w-full text-white text-center text-sm bg-blue-600 hover:bg-blue-700 active:bg-blue-800 transition-all duration-300" > This is the explorer for Hyperlane version 3.{' '} Use version 2 diff --git a/src/components/search/MiniSearchBar.tsx b/src/components/search/MiniSearchBar.tsx index 1817343..db57e4d 100644 --- a/src/components/search/MiniSearchBar.tsx +++ b/src/components/search/MiniSearchBar.tsx @@ -22,7 +22,7 @@ export function MiniSearchBar() { return ( initialValues={initialValues} onSubmit={onSubmit}>
-
+
+
{`The last step of message delivery is the recipient contract's 'handle' function. If the handle is reverting, try debugging it with `} \ No newline at end of file + \ No newline at end of file diff --git a/src/images/logos/hyperlane-name.svg b/src/images/logos/hyperlane-name.svg index 5624c43..7fb313a 100644 --- a/src/images/logos/hyperlane-name.svg +++ b/src/images/logos/hyperlane-name.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index c4331dc..a4d38b8 100755 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -11,7 +11,7 @@ import { ErrorBoundary } from '../components/errors/ErrorBoundary'; import { AppLayout } from '../components/layout/AppLayout'; import { config } from '../consts/config'; import { ChainConfigSyncer } from '../features/chains/ChainConfigSyncer'; -import '../styles/fonts.css'; +import { MAIN_FONT } from '../styles/fonts'; import '../styles/global.css'; import { useIsSsr } from '../utils/ssr'; @@ -35,19 +35,23 @@ export default function App({ Component, router, pageProps }: AppProps) { return
; } + // Note, the font definition is required both here and in _document.tsx + // Otherwise Next.js will not load the font return ( - - - - - - - - - - - - - +
+ + + + + + + + + + + + + +
); } diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index b293d14..ba92baa 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -1,6 +1,7 @@ import { Head, Html, Main, NextScript } from 'next/document'; import { links } from '../consts/links'; +import { MAIN_FONT } from '../styles/fonts'; export default function Document() { return ( @@ -40,7 +41,7 @@ export default function Document() { content="The official interchain explorer for the Hyperlane protocol and network." /> - +
diff --git a/src/styles/Color.ts b/src/styles/Color.ts index 22b89cf..e507117 100644 --- a/src/styles/Color.ts +++ b/src/styles/Color.ts @@ -1,27 +1,16 @@ -// Should match tailwind.config.js -export enum Color { - Black = '#010101', - White = '#FFFFFF', - Gray = '#6B7280', - Blue = '#2362C1', - Pink = '#D631B9', - Beige = '#F1EDE9', - Red = '#BF1B15', -} +// @ts-ignore +import { theme } from '../../tailwind.config'; -// Useful for cases when using class names isn't convenient -// such as in svg fills -export function classNameToColor(className) { - switch (className) { - case 'bg-blue-500': - return Color.Blue; - case 'bg-pink-500': - return Color.Pink; - case 'bg-red-500': - return Color.Red; - case 'bg-gray-500': - return Color.Gray; - default: - throw new Error('Missing color for className: ' + className); - } -} +const themeColors = theme.extend.colors as unknown as Record; + +export const Color = { + black: themeColors.black, + white: themeColors.white, + gray: themeColors.gray[500], + lightGray: themeColors.gray[200], + primary: themeColors.blue[500], + accent: themeColors.pink[500], + blue: themeColors.blue[200], + pink: themeColors.pink[200], + red: themeColors.red[500], +} as const; diff --git a/src/styles/fonts.css b/src/styles/fonts.css deleted file mode 100644 index af2f5ff..0000000 --- a/src/styles/fonts.css +++ /dev/null @@ -1,46 +0,0 @@ -@font-face { - font-family: 'Neue Haas Grotesk'; - font-style: normal; - font-weight: 200; - src: url('/fonts/NeueHaasDisplayThin.woff2') format('woff2'), - url('/fonts/NeueHaasDisplayThin.woff') format('woff'); -} - -@font-face { - font-family: 'Neue Haas Grotesk'; - font-style: normal; - font-weight: 300; - src: url('/fonts/NeueHaasDisplayLight.woff2') format('woff2'), - url('/fonts/NeueHaasDisplayLight.woff') format('woff'); -} - -@font-face { - font-family: 'Neue Haas Grotesk'; - font-style: normal; - font-weight: 400; - src: url('/fonts/NeueHaasDisplayRoman.woff2') format('woff2'), - url('/fonts/NeueHaasDisplayRoman.woff') format('woff'); -} - -@font-face { - font-family: 'Neue Haas Grotesk'; - font-style: normal; - font-weight: 500; - src: url('/fonts/NeueHaasDisplayMedium.woff2') format('woff2'), - url('/fonts/NeueHaasDisplayMedium.woff') format('woff'); -} - -@font-face { - font-family: 'Neue Haas Grotesk'; - font-style: normal; - font-weight: 600; - src: url('/fonts/NeueHaasDisplayMedium.ttf'); -} - -@font-face { - font-family: 'Neue Haas Grotesk'; - font-style: normal; - font-weight: 700; - src: url('/fonts/NeueHaasDisplayBold.woff2') format('woff2'), - url('/fonts/NeueHaasDisplayBold.woff') format('woff'); -} diff --git a/src/styles/fonts.ts b/src/styles/fonts.ts new file mode 100644 index 0000000..2a8647a --- /dev/null +++ b/src/styles/fonts.ts @@ -0,0 +1,8 @@ +import { Space_Grotesk } from 'next/font/google'; + +export const MAIN_FONT = Space_Grotesk({ + subsets: ['latin'], + variable: '--font-main', + preload: true, + fallback: ['sans-serif'], +}); diff --git a/tailwind.config.js b/tailwind.config.js index 5fe57d9..1cd12f3 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -6,12 +6,12 @@ module.exports = { content: ['./src/**/*.{js,ts,jsx,tsx}'], theme: { fontFamily: { - sans: ['Neue Haas Grotesk', 'Helvetica', 'sans-serif'], + sans: ['var(--font-main)'], serif: ['Garamond', 'serif'], mono: ['Courier New', 'monospace'], }, screens: { - any: '1px', + all: '1px', xs: '480px', ...defaultTheme.screens, }, @@ -19,6 +19,7 @@ module.exports = { colors: { black: '#010101', white: '#ffffff', + gray: {...defaultTheme.colors.gray, 150: '#EBEDF0', 250: '#404040', 350: '#6B6B6B'}, blue: { 50: '#E6EDF9', 100: '#CDDCF4', diff --git a/yarn.lock b/yarn.lock index 4daafba..ecb6b36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2095,13 +2095,13 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/core@npm:5.2.1": - version: 5.2.1 - resolution: "@hyperlane-xyz/core@npm:5.2.1" +"@hyperlane-xyz/core@npm:5.3.0": + version: 5.3.0 + resolution: "@hyperlane-xyz/core@npm:5.3.0" dependencies: "@arbitrum/nitro-contracts": "npm:^1.2.1" "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:5.2.1" + "@hyperlane-xyz/utils": "npm:5.3.0" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@openzeppelin/contracts": "npm:^4.9.3" "@openzeppelin/contracts-upgradeable": "npm:^v4.9.3" @@ -2110,7 +2110,7 @@ __metadata: "@ethersproject/abi": "*" "@ethersproject/providers": "*" "@types/sinon-chai": "*" - checksum: df515d545c3a174dbadef13132a63874f0fc1e2c9cf891bf9c874ab2d4b31cc1d2cb33d660f9e99d547cc6d508f527d4dd5e1e758fc8d141de56401f4616010d + checksum: f3d4ca187d59cb3f7f6c7767a7f4204b5ee17b9593b1f36272cbbaf34261b3055f5d51f55ecc590591b817b8f3a4c2aa8b63af9aee16864519df50c6faf5707a languageName: node linkType: hard @@ -2119,10 +2119,10 @@ __metadata: resolution: "@hyperlane-xyz/explorer@workspace:." dependencies: "@headlessui/react": "npm:^1.7.17" - "@hyperlane-xyz/registry": "npm:4.3.6" - "@hyperlane-xyz/sdk": "npm:5.2.1" - "@hyperlane-xyz/utils": "npm:5.2.1" - "@hyperlane-xyz/widgets": "npm:5.2.1" + "@hyperlane-xyz/registry": "npm:4.4.1" + "@hyperlane-xyz/sdk": "npm:5.3.0" + "@hyperlane-xyz/utils": "npm:5.3.0" + "@hyperlane-xyz/widgets": "npm:5.3.0" "@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6" "@tanstack/react-query": "npm:^5.35.5" "@trivago/prettier-plugin-sort-imports": "npm:^4.1.1" @@ -2160,36 +2160,36 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/registry@npm:4.3.2": - version: 4.3.2 - resolution: "@hyperlane-xyz/registry@npm:4.3.2" +"@hyperlane-xyz/registry@npm:4.3.6": + version: 4.3.6 + resolution: "@hyperlane-xyz/registry@npm:4.3.6" dependencies: yaml: "npm:2.4.5" zod: "npm:^3.21.2" - checksum: 7b1ff07074e4499f74a4c75dbbf0b7e641b3610bfc2a67785db724748d887d7b03c9dc9738b790cb56e008d6453789432c863f3b251498f77c931f196b9dab86 + checksum: 7cc42813f4f8b8ef09266be249f3dcec0584832166419df2f48eec3cc43ba766e58845ecc16673bf6465a711f08ff6c4fc5216da2f704bc31ef8ade52af4b6e5 languageName: node linkType: hard -"@hyperlane-xyz/registry@npm:4.3.6": - version: 4.3.6 - resolution: "@hyperlane-xyz/registry@npm:4.3.6" +"@hyperlane-xyz/registry@npm:4.4.1": + version: 4.4.1 + resolution: "@hyperlane-xyz/registry@npm:4.4.1" dependencies: yaml: "npm:2.4.5" zod: "npm:^3.21.2" - checksum: 7cc42813f4f8b8ef09266be249f3dcec0584832166419df2f48eec3cc43ba766e58845ecc16673bf6465a711f08ff6c4fc5216da2f704bc31ef8ade52af4b6e5 + checksum: 010dea0e55e2ae1e90cfc710527c66335736dbd0438234f33fc57fd3c3a146236b849d78ae9942db88bc648a0a7fcb9a37dcaa28acb44c929487b1a098ffdf53 languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:5.2.1": - version: 5.2.1 - resolution: "@hyperlane-xyz/sdk@npm:5.2.1" +"@hyperlane-xyz/sdk@npm:5.3.0": + version: 5.3.0 + resolution: "@hyperlane-xyz/sdk@npm:5.3.0" dependencies: "@arbitrum/sdk": "npm:^4.0.0" "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.32.4" "@cosmjs/stargate": "npm:^0.32.4" - "@hyperlane-xyz/core": "npm:5.2.1" - "@hyperlane-xyz/utils": "npm:5.2.1" + "@hyperlane-xyz/core": "npm:5.3.0" + "@hyperlane-xyz/utils": "npm:5.3.0" "@safe-global/api-kit": "npm:1.3.0" "@safe-global/protocol-kit": "npm:1.3.0" "@safe-global/safe-deployments": "npm:1.37.8" @@ -2208,13 +2208,13 @@ __metadata: peerDependencies: "@ethersproject/abi": "*" "@ethersproject/providers": "*" - checksum: 94912ab970d911d77590709f78f43139717abf2d1d1ffd4f97137d2b739a0de785517a38108e2c47e4a6090c8c7e2c63f5665aa77333ca243e5ab610b0ce2585 + checksum: f72eb29ddceff027c9c6a2a85679aaca0beff94ea171cc5653bc3f57e53350e69ba5ab11484b9fff3e3f764a9cbcf51db0fa3ab71fbe92707e2dc209e61af3e6 languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:5.2.1": - version: 5.2.1 - resolution: "@hyperlane-xyz/utils@npm:5.2.1" +"@hyperlane-xyz/utils@npm:5.3.0": + version: 5.3.0 + resolution: "@hyperlane-xyz/utils@npm:5.3.0" dependencies: "@cosmjs/encoding": "npm:^0.32.4" "@solana/web3.js": "npm:^1.78.0" @@ -2223,20 +2223,20 @@ __metadata: lodash-es: "npm:^4.17.21" pino: "npm:^8.19.0" yaml: "npm:2.4.5" - checksum: 67c725cbb0581f6a8b42723974986b9c7beee69a51dcc3261efc9c4961f27493eb10108407b1d54329eb95c19c5595ca6cc581137f925773ec600eb72c761fe1 + checksum: 2ae64331824f21c8741c2e94eadd1e863c5ce6f0b4d84e35b03eeae47b2473403636eda0f5aa648523a312b605de2a69fb9fff73949e26e3410e85b4c2fe0d27 languageName: node linkType: hard -"@hyperlane-xyz/widgets@npm:5.2.1": - version: 5.2.1 - resolution: "@hyperlane-xyz/widgets@npm:5.2.1" +"@hyperlane-xyz/widgets@npm:5.3.0": + version: 5.3.0 + resolution: "@hyperlane-xyz/widgets@npm:5.3.0" dependencies: - "@hyperlane-xyz/registry": "npm:4.3.2" - "@hyperlane-xyz/sdk": "npm:5.2.1" + "@hyperlane-xyz/registry": "npm:4.3.6" + "@hyperlane-xyz/sdk": "npm:5.3.0" peerDependencies: react: ^18 react-dom: ^18 - checksum: 9ee820be26d51cf59285ca370c88867c02b828473a6ffb136590d9d7987a109b9b956620833945ca32df2345fda3d5b1a4352d2a3e94a519b633fb2fd6f1342f + checksum: 231d03f88451dc0b0defa1002c6f0da058fb999a75b80457d7be9e1f6b381bc6e5ba9745aab54f56d3efb3be07682eb58301fa6c61b32ba2b8920aa46b6aa5d5 languageName: node linkType: hard From 0bace4598d18a873025233d47125968fce794e97 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 2 Oct 2024 15:36:49 -0400 Subject: [PATCH 02/14] Upgrade headless and tailwind Migrate to widgets layout components --- package.json | 9 +- src/{components/layout => }/AppLayout.tsx | 6 +- src/components/layout/Card.tsx | 2 +- src/components/layout/Dropdown.tsx | 88 ------ src/components/layout/Modal.tsx | 66 ----- src/components/nav/Header.tsx | 41 ++- src/components/search/SearchFilterBar.tsx | 227 +++----------- src/features/chains/ConfigureChains.tsx | 9 +- .../chains/queries/useScrapedChains.ts | 33 ++- src/features/chains/utils.ts | 7 - .../messages/cards/TransactionCard.tsx | 9 +- src/pages/_app.tsx | 2 +- src/styles/global.css | 13 + yarn.lock | 279 ++++++++++++++---- 14 files changed, 345 insertions(+), 446 deletions(-) rename src/{components/layout => }/AppLayout.tsx (92%) delete mode 100644 src/components/layout/Dropdown.tsx delete mode 100644 src/components/layout/Modal.tsx diff --git a/package.json b/package.json index 5103f9a..e352dc6 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,11 @@ "version": "5.3.0", "author": "J M Rossy", "dependencies": { - "@headlessui/react": "^1.7.17", + "@headlessui/react": "^2.1.8", "@hyperlane-xyz/registry": "4.4.1", "@hyperlane-xyz/sdk": "5.3.0", "@hyperlane-xyz/utils": "5.3.0", "@hyperlane-xyz/widgets": "5.3.0", - "@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6", "@tanstack/react-query": "^5.35.5", "bignumber.js": "^9.1.2", "buffer": "^6.0.3", @@ -23,9 +22,9 @@ "react-toastify": "^9.1.1", "react-tooltip": "^5.26.3", "urql": "^3.0.3", - "yaml": "^2.4.2", + "yaml": "^2.4.5", "zod": "^3.21.2", - "zustand": "4.3.8" + "zustand": "^4.5.5" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.1.1", @@ -42,7 +41,7 @@ "jest": "^29.6.3", "postcss": "^8.4.21", "prettier": "^2.8.4", - "tailwindcss": "^3.3.3", + "tailwindcss": "^3.4.13", "ts-node": "^10.9.1", "typescript": "^5.5.4" }, diff --git a/src/components/layout/AppLayout.tsx b/src/AppLayout.tsx similarity index 92% rename from src/components/layout/AppLayout.tsx rename to src/AppLayout.tsx index d4c29e8..7e9e16b 100644 --- a/src/components/layout/AppLayout.tsx +++ b/src/AppLayout.tsx @@ -3,8 +3,8 @@ import { PropsWithChildren } from 'react'; import { toTitleCase } from '@hyperlane-xyz/utils'; -import { Footer } from '../nav/Footer'; -import { Header } from '../nav/Header'; +import { Footer } from './components/nav/Footer'; +import { Header } from './components/nav/Header'; interface Props { pathName: string; @@ -25,7 +25,7 @@ export function AppLayout({ pathName, children }: PropsWithChildren) { {/* */}
-
+
{children}
diff --git a/src/components/layout/Card.tsx b/src/components/layout/Card.tsx index 8870062..152598b 100644 --- a/src/components/layout/Card.tsx +++ b/src/components/layout/Card.tsx @@ -7,6 +7,6 @@ interface Props { export function Card({ className, padding = 'p-4 sm:p-5', children }: PropsWithChildren) { return ( -
{children}
+
{children}
); } diff --git a/src/components/layout/Dropdown.tsx b/src/components/layout/Dropdown.tsx deleted file mode 100644 index 1aaf111..0000000 --- a/src/components/layout/Dropdown.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { Menu, Popover, Transition } from '@headlessui/react'; -import { Fragment, PropsWithChildren, ReactElement, ReactNode } from 'react'; - -interface MenuProps { - ButtonContent: (p: { isOpen: boolean }) => ReactElement; - buttonClasses?: string; - buttonTitle?: string; - menuItems: Array<(close: () => void) => ReactElement>; - menuClasses?: string; - isFullscreen?: boolean; -} - -// Uses Headless menu, which auto-closes on any item click -export function DropdownMenu({ - ButtonContent, - buttonClasses, - buttonTitle, - menuItems, - menuClasses, - isFullscreen, -}: MenuProps) { - const menuItemsClass = isFullscreen - ? `z-50 fixed left-0 right-0 top-20 bottom-0 w-screen bg-blue-500 focus:outline-none ${menuClasses}` - : `z-50 absolute -right-1.5 mt-3 origin-top-right rounded-md bg-white shadow-md drop-shadow-md focus:outline-none ${menuClasses}`; - - return ( - - - {({ open }) => } - - - - {menuItems.map((mi, i) => ( - {({ close }) => mi(close)} - ))} - - - - ); -} - -interface ModalProps { - buttonContent: ReactNode; - buttonClasses?: string; - buttonTitle?: string; - modalContent: (close: () => void) => ReactElement; - modalClasses?: string; -} - -// Uses Headless Popover, which is a more general purpose dropdown box -export function DropdownModal({ - buttonContent, - buttonClasses, - buttonTitle, - modalContent, - modalClasses, -}: ModalProps) { - return ( - - - {buttonContent} - - - - {({ close }) => modalContent(close)} - - - - ); -} - -function DropdownTransition({ children }: PropsWithChildren) { - return ( - - {children} - - ); -} diff --git a/src/components/layout/Modal.tsx b/src/components/layout/Modal.tsx deleted file mode 100644 index 57d97e5..0000000 --- a/src/components/layout/Modal.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Dialog, Transition } from '@headlessui/react'; -import { Fragment, PropsWithChildren } from 'react'; - -import XCircle from '../../images/icons/x-circle.svg'; -import { IconButton } from '../buttons/IconButton'; - -export function Modal({ - isOpen, - title, - close, - maxWidth, - children, -}: PropsWithChildren<{ isOpen: boolean; title?: string; close: () => void; maxWidth?: string }>) { - return ( - - - -
- - -
-
- - - {!!title && ( - - {title} - - )} - {children} -
- -
-
-
-
-
-
-
- ); -} diff --git a/src/components/nav/Header.tsx b/src/components/nav/Header.tsx index 0e3c86b..76b516a 100644 --- a/src/components/nav/Header.tsx +++ b/src/components/nav/Header.tsx @@ -2,13 +2,14 @@ import Image from 'next/image'; import Link from 'next/link'; import { PropsWithChildren, useEffect, useState } from 'react'; +import { DropdownMenu } from '@hyperlane-xyz/widgets'; + import { docLinks, links } from '../../consts/links'; import Explorer from '../../images/logos/hyperlane-explorer.svg'; import Logo from '../../images/logos/hyperlane-logo.svg'; import Name from '../../images/logos/hyperlane-name.svg'; import { Color } from '../../styles/Color'; import { HyperlaneWideChevron } from '../icons/Chevron'; -import { DropdownMenu } from '../layout/Dropdown'; import { MiniSearchBar } from '../search/MiniSearchBar'; const PAGES_EXCLUDING_SEARCH = ['/', '/debugger']; @@ -35,7 +36,7 @@ export function Header({ pathName }: { pathName: string }) { return (
@@ -81,38 +82,36 @@ export function Header({ pathName }: { pathName: string }) { {/* Dropdown menu, used on mobile */}
} + buttonClassname="hover:opacity-80 active:opacity-70 transition-all" menuItems={[ - (c: Fn) => ( - + ({ close }) => ( + Home ), - (c: Fn) => ( - + ({ close }) => ( + Settings ), - // (c: Fn) => ( + // ({ close }) => ( // // API // // ), - (c: Fn) => ( - + ({ close }) => ( + Docs ), - (c: Fn) => ( - + ({ close }) => ( + About ), ]} - menuClasses="pt-8 px-8" - isFullscreen={true} + menuClassname="!left-0 !right-0 py-7 px-8 bg-blue-500" />
@@ -120,27 +119,27 @@ export function Header({ pathName }: { pathName: string }) { ); } -function DropdownButton({ isOpen }: { isOpen: boolean }) { +function DropdownButton() { return (
@@ -162,7 +161,7 @@ function MobileNavLink({ rel={isExternal ? 'noopener noreferrer' : undefined} target={isExternal ? '_blank' : undefined} > - {children} + {children} ); } diff --git a/src/components/search/SearchFilterBar.tsx b/src/components/search/SearchFilterBar.tsx index 7b7e4c9..e4223c2 100644 --- a/src/components/search/SearchFilterBar.tsx +++ b/src/components/search/SearchFilterBar.tsx @@ -1,27 +1,18 @@ import Image from 'next/image'; import Link from 'next/link'; -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import { ChainMetadata } from '@hyperlane-xyz/sdk'; -import { arrayToObject } from '@hyperlane-xyz/utils'; +import { ChainSearchMenu, Modal, Popover } from '@hyperlane-xyz/widgets'; -import { useScrapedChains } from '../../features/chains/queries/useScrapedChains'; -import { - getChainDisplayName, - isEvmChain, - isPiChain, - isUnscrapedDbChain, -} from '../../features/chains/utils'; +import { useScrapedEvmChains } from '../../features/chains/queries/useScrapedChains'; import GearIcon from '../../images/icons/gear.svg'; import { useMultiProvider } from '../../store'; import { Color } from '../../styles/Color'; import { SolidButton } from '../buttons/SolidButton'; import { TextButton } from '../buttons/TextButton'; -import { ChainLogo } from '../icons/ChainLogo'; import { ChevronIcon } from '../icons/Chevron'; -import { CheckBox } from '../input/Checkbox'; import { DatetimeField } from '../input/DatetimeField'; -import { DropdownModal } from '../layout/Dropdown'; interface Props { originChain: string | null; @@ -46,14 +37,14 @@ export function SearchFilterBar({ }: Props) { return (
- - void; position?: string; }) { - const { scrapedChains } = useScrapedChains(); const multiProvider = useMultiProvider(); - const { chains, mainnets, testnets } = useMemo(() => { - const chains = Object.values(multiProvider.metadata); - // Filtering to EVM is necessary to prevent errors until cosmos support is added - // https://github.com/hyperlane-xyz/hyperlane-explorer/issues/61 - const scrapedEvmChains = chains.filter( - (c) => - isEvmChain(multiProvider, c.chainId) && - !isPiChain(multiProvider, scrapedChains, c.chainId) && - !isUnscrapedDbChain(multiProvider, c.chainId), - ); - const mainnets = scrapedEvmChains.filter((c) => !c.isTestnet); - const testnets = scrapedEvmChains.filter((c) => !!c.isTestnet); - // Return only evmChains because of graphql only accept query non-evm chains (with bigint type not string) - return { chains: scrapedEvmChains, mainnets, testnets }; - }, [multiProvider, scrapedChains]); + const { chains } = useScrapedEvmChains(multiProvider); - // Need local state as buffer before user hits apply - const [checkedChains, setCheckedChains] = useState( - value - ? arrayToObject(value.split(',')) - : arrayToObject(chains.map((c) => c.chainId.toString())), - ); - - const hasAnyUncheckedChain = (chains: ChainMetadata[]) => { - for (const c of chains) { - if (!checkedChains[c.chainId]) return true; - } - return false; - }; - - const onToggle = (chainId: string | number) => { - return (checked: boolean) => { - if (!hasAnyUncheckedChain(chains)) { - // If none are unchecked, uncheck all except this one - setCheckedChains({ [chainId]: true }); - } else { - setCheckedChains({ ...checkedChains, [chainId]: checked }); - } - }; - }; - - const onToggleSection = (chains: ChainMetadata[]) => { - return () => { - const chainIds = chains.map((c) => c.chainId.toString()); - if (hasAnyUncheckedChain(chains)) { - // If some are unchecked, check all - setCheckedChains({ ...checkedChains, ...arrayToObject(chainIds, true) }); - } else { - // If none are unchecked, uncheck all - setCheckedChains({ ...checkedChains, ...arrayToObject(chainIds, false) }); - } - }; - }; - - const onToggleAll = () => { - setCheckedChains(arrayToObject(chains.map((c) => c.chainId.toString()))); - }; + // const [checkedChain, setCheckedChain] = useState(value); - const onToggleNone = () => { - setCheckedChains({}); + const onClickChain = (c: ChainMetadata) => { + // setCheckedChain(c.chainId); + onChangeValue(c.chainId.toString()); }; - const onClickApply = (closeDropdown?: () => void) => { - const checkedList = Object.keys(checkedChains).filter((c) => !!checkedChains[c]); - if (checkedList.length === 0 || checkedList.length === chains.length) { - // Use null value, indicating to filter needed - onChangeValue(null); - } else { - onChangeValue(checkedList.join(',')); - } - if (closeDropdown) closeDropdown(); + const [showModal, setShowModal] = useState(false); + const closeModal = () => { + setShowModal(false); }; return ( - - {text} - - - } - buttonClasses="text-sm sm:min-w-[5.8rem] px-1 sm:px-2.5 py-0.5 flex items-center justify-center rounded-full bg-pink-500 hover:opacity-80 active:opacity-70 transition-all" - modalContent={(closeDropdown) => ( -
-
-

{header}

-
- - All - - - None - -
-
-
-
-
- -

Mainnet Chains

-
-
- {mainnets.map((c) => ( - -
- - {getChainDisplayName(multiProvider, c.chainId, true)} - - -
-
- ))} -
-
-
- -

Testnet Chains

-
-
- {testnets.map((c) => ( - -
- - {getChainDisplayName(multiProvider, c.chainId, true)} - - -
-
- ))} -
-
- onClickApply(closeDropdown)} - > - Apply - -
- )} - modalClasses={`w-88 ${position || 'right-0'}`} - /> + <> + + + + + ); } @@ -285,8 +144,8 @@ function DatetimeSelector({ }; return ( - Time } - buttonClasses="text-sm px-1 sm:px-2.5 py-0.5 flex items-center justify-center rounded-full bg-pink-500 hover:opacity-80 active:opacity-70 transition-all" - modalContent={(closeDropdown) => ( + buttonClassname="text-sm px-1 sm:px-2.5 py-0.5 flex items-center justify-center rounded-full bg-pink-500 hover:opacity-80 active:opacity-70 transition-all" + panelClassname="w-60" + > + {({ close }) => (

Time Range

@@ -315,15 +176,11 @@ function DatetimeSelector({

End Time

- onClickApply(closeDropdown)} - > + onClickApply(close)}> Apply
)} - modalClasses="w-60 -right-8" - /> + ); } diff --git a/src/features/chains/ConfigureChains.tsx b/src/features/chains/ConfigureChains.tsx index 53ee909..fc0df4d 100644 --- a/src/features/chains/ConfigureChains.tsx +++ b/src/features/chains/ConfigureChains.tsx @@ -1,13 +1,13 @@ import { ChangeEventHandler, useState } from 'react'; import { ChainName } from '@hyperlane-xyz/sdk'; +import { Modal } from '@hyperlane-xyz/widgets'; import { CopyButton } from '../../components/buttons/CopyButton'; import { SolidButton } from '../../components/buttons/SolidButton'; import { XIconButton } from '../../components/buttons/XIconButton'; import { ChainLogo } from '../../components/icons/ChainLogo'; import { Card } from '../../components/layout/Card'; -import { Modal } from '../../components/layout/Modal'; import { docLinks } from '../../consts/links'; import { useMultiProvider } from '../../store'; @@ -123,12 +123,7 @@ export function ConfigureChains() { setShowAddChainModal(true)}> Add custom chain - +

Input a chain metadata config including core contract addresses to enable exploration of that chain. See{' '} diff --git a/src/features/chains/queries/useScrapedChains.ts b/src/features/chains/queries/useScrapedChains.ts index 39c636d..b4040d7 100644 --- a/src/features/chains/queries/useScrapedChains.ts +++ b/src/features/chains/queries/useScrapedChains.ts @@ -1,7 +1,12 @@ -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; import { useQuery } from 'urql'; +import { ChainMetadata, MultiProvider } from '@hyperlane-xyz/sdk'; +import { objFilter } from '@hyperlane-xyz/utils'; + +import { unscrapedChainsInDb } from '../../../consts/config'; import { useStore } from '../../../store'; +import { isEvmChain, isPiChain } from '../utils'; import { DOMAINS_QUERY, DomainsEntry } from './fragments'; @@ -28,3 +33,29 @@ export function useScrapedChains() { isError: !!error, }; } + +export function useScrapedEvmChains(multiProvider: MultiProvider) { + const { scrapedChains, isFetching, isError } = useScrapedChains(); + + const { chains } = useMemo(() => { + // Filtering to EVM is necessary to prevent errors until cosmos support is added + // https://github.com/hyperlane-xyz/hyperlane-explorer/issues/61 + const scrapedEvmChains = objFilter( + multiProvider.metadata, + (chainName, chainMetadata): chainMetadata is ChainMetadata => + isEvmChain(multiProvider, chainMetadata.chainId) && + !isPiChain(multiProvider, scrapedChains, chainMetadata.chainId) && + !isUnscrapedDbChain(multiProvider, chainMetadata.chainId), + ); + // Return only evmChains because of graphql only accept query non-evm chains (with bigint type not string) + return { chains: scrapedEvmChains }; + }, [multiProvider, scrapedChains]); + + return { chains, isFetching, isError }; +} + +// TODO: Remove once all chains in the DB are scraped +export function isUnscrapedDbChain(multiProvider: MultiProvider, chainIdOrName: number | string) { + const chainName = multiProvider.tryGetChainName(chainIdOrName); + return chainName && unscrapedChainsInDb.includes(chainName); +} diff --git a/src/features/chains/utils.ts b/src/features/chains/utils.ts index 2651b7e..8469455 100644 --- a/src/features/chains/utils.ts +++ b/src/features/chains/utils.ts @@ -2,7 +2,6 @@ import { IRegistry } from '@hyperlane-xyz/registry'; import { ChainMap, MultiProvider } from '@hyperlane-xyz/sdk'; import { ProtocolType, toTitleCase } from '@hyperlane-xyz/utils'; -import { unscrapedChainsInDb } from '../../consts/config'; import { Environment } from '../../consts/environments'; import { ChainConfig } from './chainConfig'; @@ -51,9 +50,3 @@ export function isEvmChain(multiProvider: MultiProvider, chainIdOrName: number | const protocol = multiProvider.tryGetProtocol(chainIdOrName); return protocol === ProtocolType.Ethereum; } - -// TODO: Remove once all chains in the DB are scraped -export function isUnscrapedDbChain(multiProvider: MultiProvider, chainIdOrName: number | string) { - const chainName = multiProvider.tryGetChainName(chainIdOrName); - return chainName && unscrapedChainsInDb.includes(chainName); -} diff --git a/src/features/messages/cards/TransactionCard.tsx b/src/features/messages/cards/TransactionCard.tsx index f03e590..fdf7164 100644 --- a/src/features/messages/cards/TransactionCard.tsx +++ b/src/features/messages/cards/TransactionCard.tsx @@ -4,12 +4,12 @@ import { PropsWithChildren, ReactNode, useState } from 'react'; import { MultiProvider } from '@hyperlane-xyz/sdk'; import { isAddress, isZeroish } from '@hyperlane-xyz/utils'; +import { Modal } from '@hyperlane-xyz/widgets'; import { Spinner } from '../../../components/animations/Spinner'; import { ChainLogo } from '../../../components/icons/ChainLogo'; import { HelpIcon } from '../../../components/icons/HelpIcon'; import { Card } from '../../../components/layout/Card'; -import { Modal } from '../../../components/layout/Modal'; import { links } from '../../../consts/links'; import { useMultiProvider } from '../../../store'; import { MessageStatus, MessageTx } from '../../../types'; @@ -305,12 +305,7 @@ function CallDataModal({ debugResult }: { debugResult?: MessageDebugResult }) { - setIsOpen(false)} - maxWidth="max-w-sm sm:max-w-md" - > + setIsOpen(false)} panelClassname="max-w-lg p-4 sm:p-5">

{`The last step of message delivery is the recipient contract's 'handle' function. If the handle is reverting, try debugging it with `} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index a4d38b8..1f3f9f0 100755 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -7,8 +7,8 @@ import { Provider as UrqlProvider, createClient as createUrqlClient } from 'urql import '@hyperlane-xyz/widgets/styles.css'; +import { AppLayout } from '../AppLayout'; import { ErrorBoundary } from '../components/errors/ErrorBoundary'; -import { AppLayout } from '../components/layout/AppLayout'; import { config } from '../consts/config'; import { ChainConfigSyncer } from '../features/chains/ChainConfigSyncer'; import { MAIN_FONT } from '../styles/fonts'; diff --git a/src/styles/global.css b/src/styles/global.css index 6e5655e..4ec9c68 100755 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -68,6 +68,19 @@ body { } } +/* Tailwind extension to hide scrollbar */ +@layer utilities { + /* Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } +} + /* Input Overrides =============== diff --git a/yarn.lock b/yarn.lock index ecb6b36..cb40ddf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2025,6 +2025,25 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.6.0": + version: 1.6.8 + resolution: "@floating-ui/core@npm:1.6.8" + dependencies: + "@floating-ui/utils": "npm:^0.2.8" + checksum: 87d52989c3d2cc80373bc153b7a40814db3206ce7d0b2a2bdfb63e2ff39ffb8b999b1b0ccf28e548000ebf863bf16e2bed45eab4c4d287a5dbe974ef22368d82 + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.0.0": + version: 1.6.11 + resolution: "@floating-ui/dom@npm:1.6.11" + dependencies: + "@floating-ui/core": "npm:^1.6.0" + "@floating-ui/utils": "npm:^0.2.8" + checksum: 8579392ad10151474869e7640af169b0d7fc2df48d4da27b6dcb1a57202329147ed986b2972787d4b8cd550c87897271b2d9c4633c2ec7d0b3ad37ce1da636f1 + languageName: node + linkType: hard + "@floating-ui/dom@npm:^1.6.1": version: 1.6.3 resolution: "@floating-ui/dom@npm:1.6.3" @@ -2035,6 +2054,32 @@ __metadata: languageName: node linkType: hard +"@floating-ui/react-dom@npm:^2.1.2": + version: 2.1.2 + resolution: "@floating-ui/react-dom@npm:2.1.2" + dependencies: + "@floating-ui/dom": "npm:^1.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 2a67dc8499674e42ff32c7246bded185bb0fdd492150067caf9568569557ac4756a67787421d8604b0f241e5337de10762aee270d9aeef106d078a0ff13596c4 + languageName: node + linkType: hard + +"@floating-ui/react@npm:^0.26.16": + version: 0.26.24 + resolution: "@floating-ui/react@npm:0.26.24" + dependencies: + "@floating-ui/react-dom": "npm:^2.1.2" + "@floating-ui/utils": "npm:^0.2.8" + tabbable: "npm:^6.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 903ffbee2c6726d117086e2a83f43d6ad339970758ce7979fd16cc7cf8dc0f5b869bd72c2c8ee1bcd6c63b190bb0960effd4d403e63685fb5aeed6b185041b08 + languageName: node + linkType: hard + "@floating-ui/utils@npm:^0.2.0, @floating-ui/utils@npm:^0.2.1": version: 0.2.1 resolution: "@floating-ui/utils@npm:0.2.1" @@ -2042,6 +2087,13 @@ __metadata: languageName: node linkType: hard +"@floating-ui/utils@npm:^0.2.8": + version: 0.2.8 + resolution: "@floating-ui/utils@npm:0.2.8" + checksum: 3e3ea3b2de06badc4baebdf358b3dbd77ccd9474a257a6ef237277895943db2acbae756477ec64de65a2a1436d94aea3107129a1feeef6370675bf2b161c1abc + languageName: node + linkType: hard + "@gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -2058,15 +2110,18 @@ __metadata: languageName: node linkType: hard -"@headlessui/react@npm:^1.7.17": - version: 1.7.17 - resolution: "@headlessui/react@npm:1.7.17" +"@headlessui/react@npm:^2.1.8": + version: 2.1.8 + resolution: "@headlessui/react@npm:2.1.8" dependencies: - client-only: "npm:^0.0.1" + "@floating-ui/react": "npm:^0.26.16" + "@react-aria/focus": "npm:^3.17.1" + "@react-aria/interactions": "npm:^3.21.3" + "@tanstack/react-virtual": "npm:^3.8.1" peerDependencies: - react: ^16 || ^17 || ^18 - react-dom: ^16 || ^17 || ^18 - checksum: 00ad7db43bc1904a149925693f9d99d000e237d6e7206d0ded6bf43089070e0cb5b7188bef6f2f7d0d9175039dc90838f1cb9d0cc2b7479d6139da40de637fb7 + react: ^18 + react-dom: ^18 + checksum: a82f115877dcc5e3d16a6b0502b6796a5bd3f38936835e241833a538c002d4ecfc3317868b0d1e9655e5de93201b0806f51bc10dbf32604e270cda4fc1636024 languageName: node linkType: hard @@ -2118,12 +2173,11 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/explorer@workspace:." dependencies: - "@headlessui/react": "npm:^1.7.17" + "@headlessui/react": "npm:^2.1.8" "@hyperlane-xyz/registry": "npm:4.4.1" "@hyperlane-xyz/sdk": "npm:5.3.0" "@hyperlane-xyz/utils": "npm:5.3.0" "@hyperlane-xyz/widgets": "npm:5.3.0" - "@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6" "@tanstack/react-query": "npm:^5.35.5" "@trivago/prettier-plugin-sort-imports": "npm:^4.1.1" "@types/jest": "npm:^29.5.3" @@ -2150,13 +2204,13 @@ __metadata: react-dom: "npm:^18.2.0" react-toastify: "npm:^9.1.1" react-tooltip: "npm:^5.26.3" - tailwindcss: "npm:^3.3.3" + tailwindcss: "npm:^3.4.13" ts-node: "npm:^10.9.1" typescript: "npm:^5.5.4" urql: "npm:^3.0.3" - yaml: "npm:^2.4.2" + yaml: "npm:^2.4.5" zod: "npm:^3.21.2" - zustand: "npm:4.3.8" + zustand: "npm:^4.5.5" languageName: unknown linkType: soft @@ -2689,15 +2743,6 @@ __metadata: languageName: node linkType: hard -"@metamask/jazzicon@https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6": - version: 2.1.0 - resolution: "@metamask/jazzicon@https://github.com/jmrossy/jazzicon.git#commit=7a8df28974b4e81129bfbe3cab76308b889032a6" - dependencies: - mersenne-twister: "npm:^1.1.0" - checksum: 5e56251b375eade58294334783fb37a15e8fd48d792f6dc93f7247b8897541324f9cf2d3f1d9b1cffdac1d932a8bc48a89dee7cdbd6e4a312ca2ff85df90131b - languageName: node - linkType: hard - "@next/env@npm:13.5.6": version: 13.5.6 resolution: "@next/env@npm:13.5.6" @@ -3009,6 +3054,81 @@ __metadata: languageName: node linkType: hard +"@react-aria/focus@npm:^3.17.1": + version: 3.18.3 + resolution: "@react-aria/focus@npm:3.18.3" + dependencies: + "@react-aria/interactions": "npm:^3.22.3" + "@react-aria/utils": "npm:^3.25.3" + "@react-types/shared": "npm:^3.25.0" + "@swc/helpers": "npm:^0.5.0" + clsx: "npm:^2.0.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + checksum: b11632e638de2f40ec12a4a8c818059b9bf7e90b288a93b46985350c887ae7ecdf037391537f86fbacb2a186dec7e7c41a8f2ff767fd232a8cac3189f03735b2 + languageName: node + linkType: hard + +"@react-aria/interactions@npm:^3.21.3, @react-aria/interactions@npm:^3.22.3": + version: 3.22.3 + resolution: "@react-aria/interactions@npm:3.22.3" + dependencies: + "@react-aria/ssr": "npm:^3.9.6" + "@react-aria/utils": "npm:^3.25.3" + "@react-types/shared": "npm:^3.25.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + checksum: bc1e8381bda81c106d64bb6eebe06c244bcd6905d1be95fdc26bad1c5d83c48d1ec5159fb1cb8ea9ee7ebafc76595702e2d174f3c8394b766779c0d34bfa6de7 + languageName: node + linkType: hard + +"@react-aria/ssr@npm:^3.9.6": + version: 3.9.6 + resolution: "@react-aria/ssr@npm:3.9.6" + dependencies: + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + checksum: ea6b290346ce1e119ed9233fc0e34693d52ab9dc2509f07ab10710409b89484a544b7f26c1438802e97f3fb634844ae54638850cdd95caca0d1f5571781bf982 + languageName: node + linkType: hard + +"@react-aria/utils@npm:^3.25.3": + version: 3.25.3 + resolution: "@react-aria/utils@npm:3.25.3" + dependencies: + "@react-aria/ssr": "npm:^3.9.6" + "@react-stately/utils": "npm:^3.10.4" + "@react-types/shared": "npm:^3.25.0" + "@swc/helpers": "npm:^0.5.0" + clsx: "npm:^2.0.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + checksum: 86aed35da5cb0d48d949e40bf8226d5a6d6c92a8cdc60e3e12d524d1f3cc91ab6b54c5e1642823773cbb889fb61af7da22e89488b704b56fc5f4d8d59da7519b + languageName: node + linkType: hard + +"@react-stately/utils@npm:^3.10.4": + version: 3.10.4 + resolution: "@react-stately/utils@npm:3.10.4" + dependencies: + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + checksum: 8a56b4d0cf8d5a7a692d6f94ffff63feac2d7078fbc5642b94b0afcaaf7c8f7f4682cfe546f98265034c52576c198be5502cff3f9b145137884e50eb9ffb96d5 + languageName: node + linkType: hard + +"@react-types/shared@npm:^3.25.0": + version: 3.25.0 + resolution: "@react-types/shared@npm:3.25.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 + checksum: fa31eb6153c223210c2eee46934a63b922917bcde0ee583f2cfe59675db122c10e1cbae6549b1fea4284391fdbeca6888b36e9dc797231ad4a76def01490aea5 + languageName: node + linkType: hard + "@rushstack/eslint-patch@npm:^1.1.3": version: 1.1.4 resolution: "@rushstack/eslint-patch@npm:1.1.4" @@ -3824,6 +3944,15 @@ __metadata: languageName: node linkType: hard +"@swc/helpers@npm:^0.5.0": + version: 0.5.13 + resolution: "@swc/helpers@npm:0.5.13" + dependencies: + tslib: "npm:^2.4.0" + checksum: 6ba2f7e215d32d71fce139e2cfc426b3ed7eaa709febdeb07b97260a4c9eea4784cf047cc1271be273990b08220b576b94a42b5780947c0b3be84973a847a24d + languageName: node + linkType: hard + "@szmarczak/http-timer@npm:^4.0.5": version: 4.0.6 resolution: "@szmarczak/http-timer@npm:4.0.6" @@ -3860,6 +3989,25 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-virtual@npm:^3.8.1": + version: 3.10.8 + resolution: "@tanstack/react-virtual@npm:3.10.8" + dependencies: + "@tanstack/virtual-core": "npm:3.10.8" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 40a5d6089908096634fec2aa0cd646ca47c044c745e1b0d190ecbf9905ad2e6266ccd56c2550ed92f47349954dc11eb6930beac1354441ce7c98af81c5454d3f + languageName: node + linkType: hard + +"@tanstack/virtual-core@npm:3.10.8": + version: 3.10.8 + resolution: "@tanstack/virtual-core@npm:3.10.8" + checksum: 047e95fa72a0d341c0da8468799c176fd448481432f976a4780911bb4a2256aa4788d828f79fad78d127fe859b785189c13ca0fea10c560bf14d8ab8cb2c7790 + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -5497,7 +5645,7 @@ __metadata: languageName: node linkType: hard -"client-only@npm:0.0.1, client-only@npm:^0.0.1": +"client-only@npm:0.0.1": version: 0.0.1 resolution: "client-only@npm:0.0.1" checksum: 0c16bf660dadb90610553c1d8946a7fdfb81d624adea073b8440b7d795d5b5b08beb3c950c6a2cf16279365a3265158a236876d92bce16423c485c322d7dfaf8 @@ -5531,6 +5679,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.0.0": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: cdfb57fa6c7649bbff98d9028c2f0de2f91c86f551179541cf784b1cfdc1562dcb951955f46d54d930a3879931a980e32a46b598acaea274728dbe068deca919 + languageName: node + linkType: hard + "co@npm:^4.6.0": version: 4.6.0 resolution: "co@npm:4.6.0" @@ -6999,7 +7154,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.12": +"fast-glob@npm:^3.2.11": version: 3.2.12 resolution: "fast-glob@npm:3.2.12" dependencies: @@ -7025,6 +7180,19 @@ __metadata: languageName: node linkType: hard +"fast-glob@npm:^3.3.0": + version: 3.3.2 + resolution: "fast-glob@npm:3.3.2" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.4" + checksum: 222512e9315a0efca1276af9adb2127f02105d7288fa746145bf45e2716383fb79eb983c89601a72a399a56b7c18d38ce70457c5466218c5f13fad957cee16df + languageName: node + linkType: hard + "fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" @@ -8962,12 +9130,12 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.18.2": - version: 1.20.0 - resolution: "jiti@npm:1.20.0" +"jiti@npm:^1.21.0": + version: 1.21.6 + resolution: "jiti@npm:1.21.6" bin: jiti: bin/jiti.js - checksum: c4e59419dcf5599e599602c6c6bd0b3e19748c0bce886887cc91542ea085ef11f69a25dbda2b0ac7af8085afda34eef89ac6e9311949a01839c52a9af4352ec2 + checksum: 289b124cea411c130a14ffe88e3d38376ab44b6695616dfa0a1f32176a8f20ec90cdd6d2b9d81450fc6467cfa4d865f04f49b98452bff0f812bc400fd0ae78d6 languageName: node linkType: hard @@ -9457,13 +9625,6 @@ __metadata: languageName: node linkType: hard -"mersenne-twister@npm:^1.1.0": - version: 1.1.0 - resolution: "mersenne-twister@npm:1.1.0" - checksum: 1123526199091097102f2f91639ad7d5b3df4b098de9a4a72c835920e11ef0ce08e25737d5af1d363325a60da8804365eae8a41e03b7a46a1acc22e18fa8f261 - languageName: node - linkType: hard - "methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" @@ -12004,19 +12165,26 @@ __metadata: languageName: node linkType: hard -"tailwindcss@npm:^3.3.3": - version: 3.3.3 - resolution: "tailwindcss@npm:3.3.3" +"tabbable@npm:^6.0.0": + version: 6.2.0 + resolution: "tabbable@npm:6.2.0" + checksum: 980fa73476026e99dcacfc0d6e000d41d42c8e670faf4682496d30c625495e412c4369694f2a15cf1e5252d22de3c396f2b62edbe8d60b5dadc40d09e3f2dde3 + languageName: node + linkType: hard + +"tailwindcss@npm:^3.4.13": + version: 3.4.13 + resolution: "tailwindcss@npm:3.4.13" dependencies: "@alloc/quick-lru": "npm:^5.2.0" arg: "npm:^5.0.2" chokidar: "npm:^3.5.3" didyoumean: "npm:^1.2.2" dlv: "npm:^1.1.3" - fast-glob: "npm:^3.2.12" + fast-glob: "npm:^3.3.0" glob-parent: "npm:^6.0.2" is-glob: "npm:^4.0.3" - jiti: "npm:^1.18.2" + jiti: "npm:^1.21.0" lilconfig: "npm:^2.1.0" micromatch: "npm:^4.0.5" normalize-path: "npm:^3.0.0" @@ -12033,7 +12201,7 @@ __metadata: bin: tailwind: lib/cli.js tailwindcss: lib/cli.js - checksum: bc47f40cc33aca95fb9d523ecef0a450241e51d2259c354ac283c6a06c4dcd7edd1ffbd6f065fc496390ff3ab4dd8349c968b10cce7e11e0bde101705fa0f4f1 + checksum: 01b8dd35a65a028474c632b9ea7fb38634060a2c70f1f3fdfa2fe6ec74dec8224e2ee1178a5428182849790dad324e7a810de7301a9126946528c59d37f455cf languageName: node linkType: hard @@ -12513,12 +12681,12 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:1.2.0": - version: 1.2.0 - resolution: "use-sync-external-store@npm:1.2.0" +"use-sync-external-store@npm:1.2.2": + version: 1.2.2 + resolution: "use-sync-external-store@npm:1.2.2" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: a676216affc203876bd47981103f201f28c2731361bb186367e12d287a7566763213a8816910c6eb88265eccd4c230426eb783d64c373c4a180905be8820ed8e + checksum: 671e9c190aab9a8374a5d468c6ba17f52c38b6fae970110bc196fc1e2b57204149aea9619be49a1bb5207fb6e51d8afd19c3bcb94afe61813fed039821461dc0 languageName: node linkType: hard @@ -13247,12 +13415,12 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.4.2": - version: 2.4.2 - resolution: "yaml@npm:2.4.2" +"yaml@npm:^2.4.5": + version: 2.5.1 + resolution: "yaml@npm:2.5.1" bin: yaml: bin.mjs - checksum: 6eafbcd68dead734035f6f72af21bd820c29214caf7d8e40c595671a3c908535cef8092b9660a1c055c5833aa148aa640e0c5fa4adb5af2dacd6d28296ccd81c + checksum: 0eecb679db75ea6a989ad97715a9fa5d946972945aa6aa7d2175bca66c213b5564502ccb1cdd04b1bf816ee38b5c43e4e2fda3ff6f5e09da24dabb51ae92c57d languageName: node linkType: hard @@ -13308,19 +13476,22 @@ __metadata: languageName: node linkType: hard -"zustand@npm:4.3.8": - version: 4.3.8 - resolution: "zustand@npm:4.3.8" +"zustand@npm:^4.5.5": + version: 4.5.5 + resolution: "zustand@npm:4.5.5" dependencies: - use-sync-external-store: "npm:1.2.0" + use-sync-external-store: "npm:1.2.2" peerDependencies: - immer: ">=9.0" + "@types/react": ">=16.8" + immer: ">=9.0.6" react: ">=16.8" peerDependenciesMeta: + "@types/react": + optional: true immer: optional: true react: optional: true - checksum: 95a5335716414c8bef3a48165226ef099ca232931ab6cd1497515ee4241e8d5a8100edf5c3cc7d7131b72a07eb0484501405aa2c3222b4b93ba690cfa2b5593d + checksum: 481b8210187b69678074a1ca51107654c2379688e90407bfcb7961e0803a259742bfd0d77171c3f07e290896ad55fe9659b3863f30d34cb2572650ead1249f25 languageName: node linkType: hard From b78b86a414c991764262db80f25f293c33b82431 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 2 Oct 2024 16:12:03 -0400 Subject: [PATCH 03/14] Fix scroll bounce bug --- src/components/nav/Header.tsx | 16 +++----------- src/utils/useScrollListener.ts | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 src/utils/useScrollListener.ts diff --git a/src/components/nav/Header.tsx b/src/components/nav/Header.tsx index 76b516a..c0244be 100644 --- a/src/components/nav/Header.tsx +++ b/src/components/nav/Header.tsx @@ -1,6 +1,6 @@ import Image from 'next/image'; import Link from 'next/link'; -import { PropsWithChildren, useEffect, useState } from 'react'; +import { PropsWithChildren } from 'react'; import { DropdownMenu } from '@hyperlane-xyz/widgets'; @@ -9,6 +9,7 @@ import Explorer from '../../images/logos/hyperlane-explorer.svg'; import Logo from '../../images/logos/hyperlane-logo.svg'; import Name from '../../images/logos/hyperlane-name.svg'; import { Color } from '../../styles/Color'; +import { useScrollThresholdListener } from '../../utils/useScrollListener'; import { HyperlaneWideChevron } from '../icons/Chevron'; import { MiniSearchBar } from '../search/MiniSearchBar'; @@ -16,18 +17,7 @@ const PAGES_EXCLUDING_SEARCH = ['/', '/debugger']; export function Header({ pathName }: { pathName: string }) { // For dynamic sizing on scroll - const [animateHeader, setAnimateHeader] = useState(false); - useEffect(() => { - const listener = () => { - if (window.scrollY > 100) { - setAnimateHeader(true); - } else setAnimateHeader(false); - }; - window.addEventListener('scroll', listener); - return () => { - window.removeEventListener('scroll', listener); - }; - }, []); + const animateHeader = useScrollThresholdListener(100); const showSearch = !PAGES_EXCLUDING_SEARCH.includes(pathName); diff --git a/src/utils/useScrollListener.ts b/src/utils/useScrollListener.ts new file mode 100644 index 0000000..f8289be --- /dev/null +++ b/src/utils/useScrollListener.ts @@ -0,0 +1,38 @@ +import { useEffect, useState } from 'react'; + +export function useScrollThresholdListener(threshold: number, debounce = 500) { + const [isAboveThreshold, setIsAbove] = useState(false); + const [isDebouncing, setIsDebouncing] = useState(false); + + useEffect(() => { + let timeoutId: NodeJS.Timeout | null; + + const listener = () => { + if (isDebouncing) { + if (!timeoutId) { + setTimeout(() => { + setIsDebouncing(false); + timeoutId = null; + }, debounce); + } + return; + } + + if (window.scrollY > threshold && !isAboveThreshold) { + setIsAbove(true); + setIsDebouncing(true); + } else if (window.scrollY <= threshold && isAboveThreshold) { + setIsAbove(false); + setIsDebouncing(true); + } + }; + + window.addEventListener('scroll', listener, { passive: true }); + return () => { + window.removeEventListener('scroll', listener); + if (timeoutId) clearTimeout(timeoutId); + }; + }, [threshold, debounce, isAboveThreshold, isDebouncing]); + + return isAboveThreshold; +} From 1f2434b50385a09d6baf0b06ba54b1fcd7af714f Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 3 Oct 2024 14:34:45 -0400 Subject: [PATCH 04/14] Improve scroll listener --- src/utils/useScrollListener.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/utils/useScrollListener.ts b/src/utils/useScrollListener.ts index f8289be..fb8b9bd 100644 --- a/src/utils/useScrollListener.ts +++ b/src/utils/useScrollListener.ts @@ -8,22 +8,26 @@ export function useScrollThresholdListener(threshold: number, debounce = 500) { let timeoutId: NodeJS.Timeout | null; const listener = () => { + const handleScroll = () => { + if (window.scrollY > threshold && !isAboveThreshold) { + setIsAbove(true); + setIsDebouncing(true); + } else if (window.scrollY <= threshold && isAboveThreshold) { + setIsAbove(false); + setIsDebouncing(true); + } + }; + if (isDebouncing) { if (!timeoutId) { setTimeout(() => { setIsDebouncing(false); timeoutId = null; + handleScroll(); }, debounce); } - return; - } - - if (window.scrollY > threshold && !isAboveThreshold) { - setIsAbove(true); - setIsDebouncing(true); - } else if (window.scrollY <= threshold && isAboveThreshold) { - setIsAbove(false); - setIsDebouncing(true); + } else { + handleScroll(); } }; From 5bf09c83dfe503b30a8093cc1793a021608dfa15 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 3 Oct 2024 14:38:24 -0400 Subject: [PATCH 05/14] Redesign search chain filters --- package.json | 1 + src/components/search/SearchFilterBar.tsx | 187 +++++++++++------- .../chains/queries/useScrapedChains.ts | 2 +- src/features/chains/utils.ts | 4 +- src/store.ts | 15 +- src/styles/Color.ts | 4 +- yarn.lock | 3 +- 7 files changed, 135 insertions(+), 81 deletions(-) diff --git a/package.json b/package.json index e352dc6..858e646 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@tanstack/react-query": "^5.35.5", "bignumber.js": "^9.1.2", "buffer": "^6.0.3", + "clsx": "^2.1.1", "ethers": "^5.7.2", "formik": "^2.2.9", "graphql": "^16.6.0", diff --git a/src/components/search/SearchFilterBar.tsx b/src/components/search/SearchFilterBar.tsx index e4223c2..f825afd 100644 --- a/src/components/search/SearchFilterBar.tsx +++ b/src/components/search/SearchFilterBar.tsx @@ -1,12 +1,20 @@ -import Image from 'next/image'; +import clsx from 'clsx'; import Link from 'next/link'; import { useState } from 'react'; import { ChainMetadata } from '@hyperlane-xyz/sdk'; -import { ChainSearchMenu, Modal, Popover } from '@hyperlane-xyz/widgets'; +import { trimToLength } from '@hyperlane-xyz/utils'; +import { + ChainSearchMenu, + GearIcon, + IconButton, + Modal, + Popover, + XIcon, +} from '@hyperlane-xyz/widgets'; import { useScrapedEvmChains } from '../../features/chains/queries/useScrapedChains'; -import GearIcon from '../../images/icons/gear.svg'; +import { getChainDisplayName } from '../../features/chains/utils'; import { useMultiProvider } from '../../store'; import { Color } from '../../styles/Color'; import { SolidButton } from '../buttons/SolidButton'; @@ -37,19 +45,11 @@ export function SearchFilterBar({ }: Props) { return (

- + - -
- Settings + +
+
@@ -68,52 +68,64 @@ export function SearchFilterBar({ function ChainSelector({ text, - header, value, onChangeValue, - position, }: { text: string; - header: string; - value: string | null; // comma separated list of checked chains + value: ChainId | null; onChangeValue: (value: string | null) => void; - position?: string; }) { const multiProvider = useMultiProvider(); const { chains } = useScrapedEvmChains(multiProvider); - // const [checkedChain, setCheckedChain] = useState(value); + const [showModal, setShowModal] = useState(false); + const closeModal = () => { + setShowModal(false); + }; const onClickChain = (c: ChainMetadata) => { - // setCheckedChain(c.chainId); onChangeValue(c.chainId.toString()); + closeModal(); }; - const [showModal, setShowModal] = useState(false); - const closeModal = () => { - setShowModal(false); + const onClear = () => { + onChangeValue(null); }; + const chainName = value + ? trimToLength(getChainDisplayName(multiProvider, value, true), 12) + : undefined; + return ( - <> +
- + {value && } + - +
); } @@ -137,50 +149,79 @@ function DatetimeSelector({ setEndTime(null); }; + const onClickDirectClear = () => { + onClickClear(); + onChangeStartValue(null); + onChangeEndValue(null); + }; + const onClickApply = (closeDropdown?: () => void) => { onChangeStartValue(startTime); onChangeEndValue(endTime); if (closeDropdown) closeDropdown(); }; + const hasValue = !!startTime || !!endTime; + return ( - - Time - - - } - buttonClassname="text-sm px-1 sm:px-2.5 py-0.5 flex items-center justify-center rounded-full bg-pink-500 hover:opacity-80 active:opacity-70 transition-all" - panelClassname="w-60" - > - {({ close }) => ( -
-
-

Time Range

-
- - Clear - +
+ + Time + {!hasValue && ( + + )} + + } + buttonClassname={clsx( + 'text-sm px-2 sm:px-3 py-1 flex items-center justify-center font-medium border border-pink-500 rounded-full hover:opacity-80 active:opacity-70 transition-all', + hasValue ? ' bg-pink-500 text-white pr-7 sm:pr-8' : 'text-pink-500', + )} + panelClassname="w-60" + > + {({ close }) => ( +
+
+

Time Range

+
+ + Clear + +
+
+

Start Time

+ +

End Time

+ +
+ onClickApply(close)} + > + Apply +
-
-

Start Time

- -

End Time

- -
- onClickApply(close)}> - Apply - -
- )} - + )} + + {hasValue && } +
+ ); +} + +function ClearButton({ onClick }: { onClick: () => void }) { + return ( +
+ + + +
); } diff --git a/src/features/chains/queries/useScrapedChains.ts b/src/features/chains/queries/useScrapedChains.ts index b4040d7..51283a9 100644 --- a/src/features/chains/queries/useScrapedChains.ts +++ b/src/features/chains/queries/useScrapedChains.ts @@ -42,7 +42,7 @@ export function useScrapedEvmChains(multiProvider: MultiProvider) { // https://github.com/hyperlane-xyz/hyperlane-explorer/issues/61 const scrapedEvmChains = objFilter( multiProvider.metadata, - (chainName, chainMetadata): chainMetadata is ChainMetadata => + (_, chainMetadata): chainMetadata is ChainMetadata => isEvmChain(multiProvider, chainMetadata.chainId) && !isPiChain(multiProvider, scrapedChains, chainMetadata.chainId) && !isUnscrapedDbChain(multiProvider, chainMetadata.chainId), diff --git a/src/features/chains/utils.ts b/src/features/chains/utils.ts index 8469455..7f97cba 100644 --- a/src/features/chains/utils.ts +++ b/src/features/chains/utils.ts @@ -23,9 +23,9 @@ export function getChainDisplayName( chainOrDomainId?: ChainId | DomainId, shortName = false, fallbackToId = true, -) { +): string { const metadata = multiProvider.tryGetChainMetadata(chainOrDomainId || 0); - if (!metadata) return fallbackToId && chainOrDomainId ? chainOrDomainId : 'Unknown'; + if (!metadata) return fallbackToId && chainOrDomainId ? chainOrDomainId.toString() : 'Unknown'; const displayName = shortName ? metadata.displayNameShort : metadata.displayName; return toTitleCase(displayName || metadata.displayName || metadata.name); } diff --git a/src/store.ts b/src/store.ts index bad9208..9e2e476 100644 --- a/src/store.ts +++ b/src/store.ts @@ -2,7 +2,8 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { GithubRegistry, IRegistry } from '@hyperlane-xyz/registry'; -import { ChainMap, MultiProvider } from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainMetadata, MultiProvider } from '@hyperlane-xyz/sdk'; +import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { config } from './consts/config'; import { ChainConfig } from './features/chains/chainConfig'; @@ -91,5 +92,15 @@ async function buildMultiProvider(registry: IRegistry, customChainConfigs: Chain // TODO improve interface so this pre-cache isn't required await registry.listRegistryContent(); const registryChainMetadata = await registry.getMetadata(); - return new MultiProvider({ ...registryChainMetadata, ...customChainConfigs }); + // TODO have the registry do this automatically + const metadataWithLogos = await promiseObjAll( + objMap( + registryChainMetadata, + async (chainName, metadata): Promise => ({ + ...metadata, + logoURI: (await registry.getChainLogoUri(chainName)) || undefined, + }), + ), + ); + return new MultiProvider({ ...metadataWithLogos, ...customChainConfigs }); } diff --git a/src/styles/Color.ts b/src/styles/Color.ts index e507117..bf4db16 100644 --- a/src/styles/Color.ts +++ b/src/styles/Color.ts @@ -10,7 +10,7 @@ export const Color = { lightGray: themeColors.gray[200], primary: themeColors.blue[500], accent: themeColors.pink[500], - blue: themeColors.blue[200], - pink: themeColors.pink[200], + blue: themeColors.blue[500], + pink: themeColors.pink[500], red: themeColors.red[500], } as const; diff --git a/yarn.lock b/yarn.lock index cb40ddf..7fc31bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2189,6 +2189,7 @@ __metadata: autoprefixer: "npm:^10.4.15" bignumber.js: "npm:^9.1.2" buffer: "npm:^6.0.3" + clsx: "npm:^2.1.1" eslint: "npm:^8.41.0" eslint-config-next: "npm:^13.4.19" eslint-config-prettier: "npm:^8.8.0" @@ -5679,7 +5680,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^2.0.0": +"clsx@npm:^2.0.0, clsx@npm:^2.1.1": version: 2.1.1 resolution: "clsx@npm:2.1.1" checksum: cdfb57fa6c7649bbff98d9028c2f0de2f91c86f551179541cf784b1cfdc1562dcb951955f46d54d930a3879931a980e32a46b598acaea274728dbe068deca919 From ae86d66061586ab5fe9436ff23a4aef33c6425ce Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 3 Oct 2024 16:00:36 -0400 Subject: [PATCH 06/14] More de-deduplication with widgets lib --- src/components/buttons/BackButton.tsx | 7 - src/components/buttons/IconButton.tsx | 49 ------- src/components/buttons/XIconButton.tsx | 21 --- src/components/icons/Chevron.tsx | 134 ------------------ src/components/icons/HelpIcon.tsx | 26 ++-- src/components/icons/HyperlaneLogo.tsx | 38 ----- src/components/icons/XIcon.tsx | 31 ---- src/components/input/Checkbox.module.css | 33 ----- src/components/input/Checkbox.tsx | 28 ---- src/components/input/TextField.tsx | 11 -- src/components/nav/Footer.tsx | 15 +- src/components/nav/Header.tsx | 15 +- src/components/search/MiniSearchBar.tsx | 13 +- src/components/search/SearchBar.tsx | 15 +- src/components/search/SearchFilterBar.tsx | 10 +- src/features/chains/ConfigureChains.tsx | 11 +- .../messages/cards/ContentDetailsCard.tsx | 5 +- .../messages/cards/GasDetailsCard.tsx | 5 +- .../messages/cards/IcaDetailsCard.tsx | 2 +- .../messages/cards/IsmDetailsCard.tsx | 5 +- .../messages/cards/TransactionCard.tsx | 2 +- 21 files changed, 48 insertions(+), 428 deletions(-) delete mode 100644 src/components/buttons/BackButton.tsx delete mode 100644 src/components/buttons/IconButton.tsx delete mode 100644 src/components/buttons/XIconButton.tsx delete mode 100644 src/components/icons/Chevron.tsx delete mode 100644 src/components/icons/HyperlaneLogo.tsx delete mode 100644 src/components/icons/XIcon.tsx delete mode 100644 src/components/input/Checkbox.module.css delete mode 100644 src/components/input/Checkbox.tsx delete mode 100644 src/components/input/TextField.tsx diff --git a/src/components/buttons/BackButton.tsx b/src/components/buttons/BackButton.tsx deleted file mode 100644 index c1c4694..0000000 --- a/src/components/buttons/BackButton.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import LeftArrow from '../../images/icons/arrow-left-circle.svg'; - -import { IconButton, IconButtonProps } from './IconButton'; - -export function BackButton(props: IconButtonProps) { - return ; -} diff --git a/src/components/buttons/IconButton.tsx b/src/components/buttons/IconButton.tsx deleted file mode 100644 index cda1daa..0000000 --- a/src/components/buttons/IconButton.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import Image from 'next/image'; -import { PropsWithChildren } from 'react'; - -export interface IconButtonProps { - width?: number; - height?: number; - classes?: string; - onClick?: () => void; - disabled?: boolean; - imgSrc?: any; - title?: string; - type?: 'button' | 'submit'; - passThruProps?: any; -} - -export function IconButton(props: PropsWithChildren) { - const { - width, - height, - classes, - onClick, - imgSrc, - disabled, - title, - type, - children, - passThruProps, - } = props; - - const base = 'flex items-center justify-center transition-all'; - const onHover = 'hover:opacity-70'; - const onDisabled = 'disabled:opacity-50'; - const onActive = 'active:opacity-60'; - const allClasses = `${base} ${onHover} ${onDisabled} ${onActive} ${classes}`; - - return ( - - ); -} diff --git a/src/components/buttons/XIconButton.tsx b/src/components/buttons/XIconButton.tsx deleted file mode 100644 index 7017fa6..0000000 --- a/src/components/buttons/XIconButton.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { memo } from 'react'; - -import X from '../../images/icons/x.svg'; - -import { IconButton } from './IconButton'; - -function _XIconButton({ - onClick, - title, - size = 20, -}: { - onClick: () => void; - title?: string; - size?: number; -}) { - return ( - - ); -} - -export const XIconButton = memo(_XIconButton); diff --git a/src/components/icons/Chevron.tsx b/src/components/icons/Chevron.tsx deleted file mode 100644 index 2cf37bf..0000000 --- a/src/components/icons/Chevron.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { memo } from 'react'; - -import { Color } from '../../styles/Color'; - -interface Props { - width?: string | number; - height?: string | number; - direction: 'n' | 'e' | 's' | 'w'; - color?: string; - classes?: string; -} - -function _ChevronIcon({ width, height, direction, color, classes }: Props) { - let directionClass; - switch (direction) { - case 'n': - directionClass = 'rotate-180'; - break; - case 'e': - directionClass = '-rotate-90'; - break; - case 's': - directionClass = ''; - break; - case 'w': - directionClass = 'rotate-90'; - break; - default: - throw new Error(`Invalid chevron direction ${direction}`); - } - - return ( - - - - ); -} - -export const ChevronIcon = memo(_ChevronIcon); - -interface Props { - width?: string | number; - height?: string | number; - direction: 'n' | 'e' | 's' | 'w'; - color?: string; - classes?: string; -} - -function _HyperlaneChevron({ width, height, direction, color, classes }: Props) { - let directionClass; - switch (direction) { - case 'n': - directionClass = '-rotate-90'; - break; - case 'e': - directionClass = ''; - break; - case 's': - directionClass = 'rotate-90'; - break; - case 'w': - directionClass = 'rotate-180'; - break; - default: - throw new Error(`Invalid chevron direction ${direction}`); - } - - return ( - - - - ); -} - -export const HyperlaneChevron = memo(_HyperlaneChevron); - -function _HyperlaneWideChevron({ width, height, direction, color, classes }: Props) { - let directionClass; - switch (direction) { - case 'n': - directionClass = '-rotate-90'; - break; - case 'e': - directionClass = ''; - break; - case 's': - directionClass = 'rotate-90'; - break; - case 'w': - directionClass = 'rotate-180'; - break; - default: - throw new Error(`Invalid chevron direction ${direction}`); - } - - return ( - - - - ); -} - -export const HyperlaneWideChevron = memo(_HyperlaneWideChevron); diff --git a/src/components/icons/HelpIcon.tsx b/src/components/icons/HelpIcon.tsx index d7ead41..c101831 100644 --- a/src/components/icons/HelpIcon.tsx +++ b/src/components/icons/HelpIcon.tsx @@ -1,22 +1,26 @@ import { memo } from 'react'; -import Question from '../../images/icons/question-circle.svg'; -import { IconButton } from '../buttons/IconButton'; +import { IconButton, QuestionMarkIcon } from '@hyperlane-xyz/widgets'; -function _HelpIcon({ text, size = 20 }: { text: string; size?: number }) { +import { Color } from '../../styles/Color'; + +function _HelpIcon({ text, size = 16 }: { text: string; size?: number }) { + const tooltipProps = { + 'data-tooltip-content': text, + 'data-tooltip-id': 'root-tooltip', + 'data-tooltip-place': 'top-start', + }; return ( + // @ts-ignore allow pass-thru tooltip props + className="border border-gray-400 rounded-full p-px" + {...tooltipProps} + > + + ); } diff --git a/src/components/icons/HyperlaneLogo.tsx b/src/components/icons/HyperlaneLogo.tsx deleted file mode 100644 index 5281046..0000000 --- a/src/components/icons/HyperlaneLogo.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { memo } from 'react'; - -function _HyperlaneLogo({ - width, - height, - fill, - className = '', -}: { - width?: number | string; - height?: number | string; - fill?: string; - className?: string; -}) { - return ( - - - - - - ); -} - -export const HyperlaneLogo = memo(_HyperlaneLogo); diff --git a/src/components/icons/XIcon.tsx b/src/components/icons/XIcon.tsx deleted file mode 100644 index 311510f..0000000 --- a/src/components/icons/XIcon.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { memo } from 'react'; - -function _XIcon({ - width, - height, - fill, - className = '', -}: { - width?: number | string; - height?: number | string; - fill?: string; - className?: string; -}) { - return ( - - - - - ); -} - -export const XIcon = memo(_XIcon); diff --git a/src/components/input/Checkbox.module.css b/src/components/input/Checkbox.module.css deleted file mode 100644 index 8e35255..0000000 --- a/src/components/input/Checkbox.module.css +++ /dev/null @@ -1,33 +0,0 @@ -.checkbox { - appearance: none; - background-color: #fff; - margin: 0; - width: 0.9rem; - height: 0.9rem; - border: 2px solid #1f2937; - border-radius: 0.2rem; - transform: translateY(-0.075em); - display: grid; - place-content: center; - cursor: pointer; - transition: 200ms all ease-in-out; -} - -.checkbox::before { - content: ''; - width: 0.9rem; - height: 0.9rem; - border-radius: 0.2rem; - transform: scale(0); - transition: 200ms all ease-in-out; - background-color: #2362c0; -} - -.checkbox:checked::before { - transform: scale(1); -} - -.checkbox:disabled { - border-color: #bbb; - cursor: not-allowed; -} diff --git a/src/components/input/Checkbox.tsx b/src/components/input/Checkbox.tsx deleted file mode 100644 index 8fc8d3c..0000000 --- a/src/components/input/Checkbox.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import styles from './Checkbox.module.css'; - -interface Props { - checked: boolean; - onToggle: (c: boolean) => void; - name?: string; -} - -export function CheckBox({ checked, onToggle, name, children }: React.PropsWithChildren) { - const onChange = () => { - onToggle(!checked); - }; - - return ( - - ); -} diff --git a/src/components/input/TextField.tsx b/src/components/input/TextField.tsx deleted file mode 100644 index 84adb3d..0000000 --- a/src/components/input/TextField.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Field } from 'formik'; -import { ComponentProps } from 'react'; - -export function TextField(props: ComponentProps) { - return ( - - ); -} diff --git a/src/components/nav/Footer.tsx b/src/components/nav/Footer.tsx index ce2b119..479c1fe 100644 --- a/src/components/nav/Footer.tsx +++ b/src/components/nav/Footer.tsx @@ -2,15 +2,14 @@ import Image from 'next/image'; import Link from 'next/link'; +import { HyperlaneLogo } from '@hyperlane-xyz/widgets'; + import { docLinks, links } from '../../consts/links'; -// import FooterLine from '../../images/backgrounds/footer-line-desktop.svg'; -// import FooterLineMobile from '../../images/backgrounds/footer-line-mobile.svg'; import FooterBg from '../../images/backgrounds/footer-bg.svg'; import FooterTopBorder from '../../images/backgrounds/footer-top-border.svg'; import { Color } from '../../styles/Color'; import { Discord } from '../icons/Discord'; import { Github } from '../icons/Github'; -import { HyperlaneLogo } from '../icons/HyperlaneLogo'; import { Twitter } from '../icons/Twitter'; const footerLinks1 = [ @@ -50,20 +49,12 @@ export function Footer() {
- +
Go interchain
with Hyperlane
- {/*
-
- -
-
- -
-
*/}
diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx index 290d582..c7ef929 100644 --- a/src/components/search/SearchBar.tsx +++ b/src/components/search/SearchBar.tsx @@ -1,10 +1,10 @@ import Image from 'next/image'; import { ChangeEvent } from 'react'; +import { IconButton, XIcon } from '@hyperlane-xyz/widgets'; + import SearchIcon from '../../images/icons/search.svg'; -import XIcon from '../../images/icons/x.svg'; import { Spinner } from '../animations/Spinner'; -import { IconButton } from '../buttons/IconButton'; interface Props { value: string; @@ -36,14 +36,9 @@ export function SearchBar({ value, placeholder, onChangeValue, isFetching }: Pro )} {!isFetching && !value && } {!isFetching && value && ( - onChange(null)} - classes="invert" - /> + onChange(null)}> + + )}
diff --git a/src/components/search/SearchFilterBar.tsx b/src/components/search/SearchFilterBar.tsx index f825afd..bdbcfb5 100644 --- a/src/components/search/SearchFilterBar.tsx +++ b/src/components/search/SearchFilterBar.tsx @@ -6,6 +6,7 @@ import { ChainMetadata } from '@hyperlane-xyz/sdk'; import { trimToLength } from '@hyperlane-xyz/utils'; import { ChainSearchMenu, + ChevronIcon, GearIcon, IconButton, Modal, @@ -19,7 +20,6 @@ import { useMultiProvider } from '../../store'; import { Color } from '../../styles/Color'; import { SolidButton } from '../buttons/SolidButton'; import { TextButton } from '../buttons/TextButton'; -import { ChevronIcon } from '../icons/Chevron'; import { DatetimeField } from '../input/DatetimeField'; interface Props { @@ -101,7 +101,7 @@ function ChainSelector({
diff --git a/src/features/messages/cards/GasDetailsCard.tsx b/src/features/messages/cards/GasDetailsCard.tsx index 9caaa2d..5226f96 100644 --- a/src/features/messages/cards/GasDetailsCard.tsx +++ b/src/features/messages/cards/GasDetailsCard.tsx @@ -75,10 +75,7 @@ export function GasDetailsCard({ message, blur, igpPayments = {} }: Props) {

Interchain Gas Payments

- +

diff --git a/src/features/messages/cards/IcaDetailsCard.tsx b/src/features/messages/cards/IcaDetailsCard.tsx index ff8350d..1eec579 100644 --- a/src/features/messages/cards/IcaDetailsCard.tsx +++ b/src/features/messages/cards/IcaDetailsCard.tsx @@ -31,7 +31,7 @@ export function IcaDetailsCard({ message: { originDomainId, body }, blur }: Prop

ICA Details

- +
{decodeResult ? ( diff --git a/src/features/messages/cards/IsmDetailsCard.tsx b/src/features/messages/cards/IsmDetailsCard.tsx index 7ff24a9..744d8c1 100644 --- a/src/features/messages/cards/IsmDetailsCard.tsx +++ b/src/features/messages/cards/IsmDetailsCard.tsx @@ -22,10 +22,7 @@ export function IsmDetailsCard({ ismDetails, blur }: Props) {

Interchain Security Modules

- +

diff --git a/src/features/messages/cards/TransactionCard.tsx b/src/features/messages/cards/TransactionCard.tsx index fdf7164..bc40d30 100644 --- a/src/features/messages/cards/TransactionCard.tsx +++ b/src/features/messages/cards/TransactionCard.tsx @@ -182,7 +182,7 @@ function TransactionCard({

{title}

- +
{children} From eabdf92263a617e99740e8efeab1a30bada66cf2 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 7 Oct 2024 16:12:37 -0400 Subject: [PATCH 07/14] Use new chain widget props Replace chainConfig store state with chainMetadataOverrides --- src/components/nav/Header.tsx | 14 +- src/components/search/SearchFilterBar.tsx | 23 ++- src/components/search/SearchStates.tsx | 2 +- src/features/api/getMessages.ts | 15 +- src/features/api/getStatus.ts | 11 +- src/features/api/searchMessages.ts | 15 +- src/features/api/searchPiMessages.ts | 25 ++- src/features/api/types.ts | 10 - src/features/api/utils.ts | 8 - src/features/chains/ChainConfigSyncer.tsx | 2 +- src/features/chains/ConfigureChains.tsx | 182 ------------------ src/features/chains/chainConfig.ts | 96 --------- src/features/chains/chainconfig.test.ts | 32 --- .../chains/queries/useScrapedChains.ts | 3 +- ...useChainConfigs.ts => useChainMetadata.ts} | 47 +++-- src/features/chains/utils.ts | 7 +- src/features/debugger/debugMessage.ts | 7 +- .../deliveryStatus/fetchDeliveryStatus.ts | 9 +- .../useMessageDeliveryStatus.tsx | 7 +- .../pi-queries/fetchPiChainMessages.test.ts | 10 +- .../pi-queries/fetchPiChainMessages.ts | 45 +++-- .../pi-queries/usePiChainMessageQuery.ts | 13 +- .../backgrounds/footer-line-desktop.svg | 3 - src/images/backgrounds/footer-line-mobile.svg | 3 - src/multiProvider.ts | 0 src/pages/settings.tsx | 13 -- src/store.ts | 48 +++-- 27 files changed, 156 insertions(+), 494 deletions(-) delete mode 100644 src/features/chains/ConfigureChains.tsx delete mode 100644 src/features/chains/chainConfig.ts delete mode 100644 src/features/chains/chainconfig.test.ts rename src/features/chains/{useChainConfigs.ts => useChainMetadata.ts} (58%) delete mode 100644 src/images/backgrounds/footer-line-desktop.svg delete mode 100644 src/images/backgrounds/footer-line-mobile.svg create mode 100644 src/multiProvider.ts delete mode 100644 src/pages/settings.tsx diff --git a/src/components/nav/Header.tsx b/src/components/nav/Header.tsx index 63ae2a6..bc12d2d 100644 --- a/src/components/nav/Header.tsx +++ b/src/components/nav/Header.tsx @@ -63,9 +63,9 @@ export function Header({ pathName }: { pathName: string }) { > Docs - + {/* Settings - + */} {showSearch && } {/* Dropdown menu, used on mobile */} @@ -79,11 +79,11 @@ export function Header({ pathName }: { pathName: string }) { Home ), - ({ close }) => ( - - Settings - - ), + // ({ close }) => ( + // + // Settings + // + // ), // ({ close }) => ( // // API diff --git a/src/components/search/SearchFilterBar.tsx b/src/components/search/SearchFilterBar.tsx index bdbcfb5..cb4f186 100644 --- a/src/components/search/SearchFilterBar.tsx +++ b/src/components/search/SearchFilterBar.tsx @@ -1,5 +1,4 @@ import clsx from 'clsx'; -import Link from 'next/link'; import { useState } from 'react'; import { ChainMetadata } from '@hyperlane-xyz/sdk'; @@ -7,7 +6,6 @@ import { trimToLength } from '@hyperlane-xyz/utils'; import { ChainSearchMenu, ChevronIcon, - GearIcon, IconButton, Modal, Popover, @@ -16,7 +14,7 @@ import { import { useScrapedEvmChains } from '../../features/chains/queries/useScrapedChains'; import { getChainDisplayName } from '../../features/chains/utils'; -import { useMultiProvider } from '../../store'; +import { useMultiProvider, useStore } from '../../store'; import { Color } from '../../styles/Color'; import { SolidButton } from '../buttons/SolidButton'; import { TextButton } from '../buttons/TextButton'; @@ -57,11 +55,6 @@ export function SearchFilterBar({ endValue={endTimestamp} onChangeEndValue={onChangeEndTimestamp} /> - -
- -
-
); } @@ -77,6 +70,10 @@ function ChainSelector({ }) { const multiProvider = useMultiProvider(); const { chains } = useScrapedEvmChains(multiProvider); + const { chainMetadataOverrides, setChainMetadataOverrides } = useStore((s) => ({ + chainMetadataOverrides: s.chainMetadataOverrides, + setChainMetadataOverrides: s.setChainMetadataOverrides, + })); const [showModal, setShowModal] = useState(false); const closeModal = () => { @@ -121,9 +118,15 @@ function ChainSelector({ - +
); diff --git a/src/components/search/SearchStates.tsx b/src/components/search/SearchStates.tsx index 5378680..a4c1ecc 100644 --- a/src/components/search/SearchStates.tsx +++ b/src/components/search/SearchStates.tsx @@ -18,7 +18,7 @@ export function SearchFetching({ show, isPiFetching }: { show: boolean; isPiFetc
- {isPiFetching ? 'Searching custom chains for messages' : 'Searching for messages'} + {isPiFetching ? 'Searching override chains for messages' : 'Searching for messages'}
diff --git a/src/features/api/getMessages.ts b/src/features/api/getMessages.ts index 8231105..bc895f1 100644 --- a/src/features/api/getMessages.ts +++ b/src/features/api/getMessages.ts @@ -1,6 +1,8 @@ import { Client } from '@urql/core'; import type { NextApiRequest } from 'next'; +import { Result, failure, success } from '@hyperlane-xyz/utils'; + import { API_GRAPHQL_QUERY_LIMIT } from '../../consts/api'; import { logger } from '../../utils/logger'; import { sanitizeString } from '../../utils/string'; @@ -8,15 +10,12 @@ import { MessageIdentifierType, buildMessageQuery } from '../messages/queries/bu import { MessagesQueryResult } from '../messages/queries/fragments'; import { parseMessageQueryResult } from '../messages/queries/parse'; -import { ApiHandlerResult, ApiMessage, toApiMessage } from './types'; -import { failureResult, getMultiProvider, getScrapedChains, successResult } from './utils'; +import { ApiMessage, toApiMessage } from './types'; +import { getMultiProvider, getScrapedChains } from './utils'; -export async function handler( - req: NextApiRequest, - client: Client, -): Promise> { +export async function handler(req: NextApiRequest, client: Client): Promise> { const identifierParam = parseQueryParams(req); - if (!identifierParam) return failureResult('No message identifier param provided'); + if (!identifierParam) return failure('No message identifier param provided'); logger.debug('Attempting to find messages matching:', identifierParam); const { query, variables } = buildMessageQuery( @@ -30,7 +29,7 @@ export async function handler( const scrapedChains = await getScrapedChains(client); const messages = parseMessageQueryResult(multiProvider, scrapedChains, result.data); - return successResult(messages.map(toApiMessage)); + return success(messages.map(toApiMessage)); } // TODO replace with Zod diff --git a/src/features/api/getStatus.ts b/src/features/api/getStatus.ts index 0052419..4259575 100644 --- a/src/features/api/getStatus.ts +++ b/src/features/api/getStatus.ts @@ -1,6 +1,8 @@ import { Client } from '@urql/core'; import type { NextApiRequest } from 'next'; +import { Result, failure, success } from '@hyperlane-xyz/utils'; + import { API_GRAPHQL_QUERY_LIMIT } from '../../consts/api'; import { MessageStatus } from '../../types'; import { logger } from '../../utils/logger'; @@ -9,8 +11,7 @@ import { MessagesStubQueryResult } from '../messages/queries/fragments'; import { parseMessageStubResult } from '../messages/queries/parse'; import { parseQueryParams } from './getMessages'; -import { ApiHandlerResult } from './types'; -import { failureResult, getMultiProvider, getScrapedChains, successResult } from './utils'; +import { getMultiProvider, getScrapedChains } from './utils'; interface MessageStatusResult { id: string; @@ -20,9 +21,9 @@ interface MessageStatusResult { export async function handler( req: NextApiRequest, client: Client, -): Promise> { +): Promise> { const identifierParam = parseQueryParams(req); - if (!identifierParam) return failureResult('No message identifier param provided'); + if (!identifierParam) return failure('No message identifier param provided'); logger.debug('Attempting to find message status matching:', identifierParam); const { query, variables } = buildMessageQuery( @@ -38,5 +39,5 @@ export async function handler( const messages = parseMessageStubResult(multiProvider, scrapedChains, result.data); - return successResult(messages.map((m) => ({ id: m.msgId, status: m.status }))); + return success(messages.map((m) => ({ id: m.msgId, status: m.status }))); } diff --git a/src/features/api/searchMessages.ts b/src/features/api/searchMessages.ts index 4a17719..830151e 100644 --- a/src/features/api/searchMessages.ts +++ b/src/features/api/searchMessages.ts @@ -1,6 +1,8 @@ import { Client } from '@urql/core'; import type { NextApiRequest } from 'next'; +import { Result, failure, success } from '@hyperlane-xyz/utils'; + import { API_GRAPHQL_QUERY_LIMIT } from '../../consts/api'; import { logger } from '../../utils/logger'; import { sanitizeString } from '../../utils/string'; @@ -8,17 +10,14 @@ import { buildMessageSearchQuery } from '../messages/queries/build'; import { MessagesQueryResult } from '../messages/queries/fragments'; import { parseMessageQueryResult } from '../messages/queries/parse'; -import { ApiHandlerResult, ApiMessage, toApiMessage } from './types'; -import { failureResult, getMultiProvider, getScrapedChains, successResult } from './utils'; +import { ApiMessage, toApiMessage } from './types'; +import { getMultiProvider, getScrapedChains } from './utils'; const SEARCH_QUERY_PARAM_NAME = 'query'; -export async function handler( - req: NextApiRequest, - client: Client, -): Promise> { +export async function handler(req: NextApiRequest, client: Client): Promise> { const queryValue = parseSearchQueryParam(req); - if (!queryValue) return failureResult('No query param provided'); + if (!queryValue) return failure('No query param provided'); logger.debug('Attempting to search for messages:', queryValue); // TODO consider supporting time/chain filters here @@ -37,7 +36,7 @@ export async function handler( const messages = parseMessageQueryResult(multiProvider, scrapedChains, result.data); - return successResult(messages.map(toApiMessage)); + return success(messages.map(toApiMessage)); } // TODO replace with Zod diff --git a/src/features/api/searchPiMessages.ts b/src/features/api/searchPiMessages.ts index 70e1053..5bf3c42 100644 --- a/src/features/api/searchPiMessages.ts +++ b/src/features/api/searchPiMessages.ts @@ -2,18 +2,17 @@ import type { NextApiRequest } from 'next'; import { z } from 'zod'; import { GithubRegistry } from '@hyperlane-xyz/registry'; -import { MultiProvider } from '@hyperlane-xyz/sdk'; +import { ChainMetadataSchema, MultiProvider } from '@hyperlane-xyz/sdk'; +import { Result, failure, success } from '@hyperlane-xyz/utils'; import { config } from '../../consts/config'; import { logger } from '../../utils/logger'; -import { tryParseChainConfig } from '../chains/chainConfig'; import { PiMessageQuery, fetchMessagesFromPiChain, } from '../messages/pi-queries/fetchPiChainMessages'; -import { ApiHandlerResult, ApiMessage } from './types'; -import { failureResult, successResult } from './utils'; +import { ApiMessage } from './types'; const queryParamSchema = z.object({ query: z.string(), @@ -21,25 +20,25 @@ const queryParamSchema = z.object({ toBlock: z.string().optional(), }); -export async function handler(req: NextApiRequest): Promise> { +export async function handler(req: NextApiRequest): Promise> { const query = tryParseParams(req); - if (!query) return failureResult('Invalid query params provided'); + if (!query) return failure('Invalid query params provided'); - const parseResult = tryParseChainConfig(req.body); - if (!parseResult.success) return failureResult(`Invalid chain configs: ${parseResult.error}`); - const chainConfig = parseResult.chainConfig; + const parseResult = ChainMetadataSchema.safeParse(req.body); + if (!parseResult.success) return failure(`Invalid chain configs: ${parseResult.error}`); + const chainMetadata = parseResult.data; try { logger.debug('Attempting to search for PI messages:', query); - const multiProvider = new MultiProvider({ [chainConfig.name]: chainConfig }); + const multiProvider = new MultiProvider({ [chainMetadata.name]: chainMetadata }); const registry = new GithubRegistry({ proxyUrl: config.githubProxy }); // TODO consider supporting block/time/chain filters here - const messages = await fetchMessagesFromPiChain(chainConfig, query, multiProvider, registry); + const messages = await fetchMessagesFromPiChain(chainMetadata, query, multiProvider, registry); logger.debug(`Found ${messages.length} PI messages`); - return successResult(messages); + return success(messages); } catch (error) { logger.error('Error fetching PI messages', error); - return failureResult('Unable to fetch messages, check config and query'); + return failure('Unable to fetch messages, check config and query'); } } diff --git a/src/features/api/types.ts b/src/features/api/types.ts index efece9a..e5578c6 100644 --- a/src/features/api/types.ts +++ b/src/features/api/types.ts @@ -1,15 +1,5 @@ import { Message } from '../../types'; -export type ApiHandlerResult = - | { - success: true; - data: T; - } - | { - success: false; - error: string; - }; - export type ApiMessage = Omit< Message, | 'msgId' // use id field for msgId diff --git a/src/features/api/utils.ts b/src/features/api/utils.ts index 4334e30..dbae248 100644 --- a/src/features/api/utils.ts +++ b/src/features/api/utils.ts @@ -7,14 +7,6 @@ import { config } from '../../consts/config'; import { logger } from '../../utils/logger'; import { DOMAINS_QUERY, DomainsEntry } from '../chains/queries/fragments'; -export function successResult(data: R): { success: true; data: R } { - return { success: true, data }; -} - -export function failureResult(error: string): { success: false; error: string } { - return { success: false, error }; -} - // TODO de-dupe this with store.ts and handle registry/multiProvider concerns in a single place export async function getMultiProvider(): Promise { const registry = new GithubRegistry({ proxyUrl: config.githubProxy }); diff --git a/src/features/chains/ChainConfigSyncer.tsx b/src/features/chains/ChainConfigSyncer.tsx index 68658e0..1a97ed2 100644 --- a/src/features/chains/ChainConfigSyncer.tsx +++ b/src/features/chains/ChainConfigSyncer.tsx @@ -1,6 +1,6 @@ import { PropsWithChildren } from 'react'; -import { useQueryParamChainConfigSync } from './useChainConfigs'; +import { useQueryParamChainConfigSync } from './useChainMetadata'; export function ChainConfigSyncer({ children }: PropsWithChildren>) { useQueryParamChainConfigSync(); diff --git a/src/features/chains/ConfigureChains.tsx b/src/features/chains/ConfigureChains.tsx deleted file mode 100644 index 9511478..0000000 --- a/src/features/chains/ConfigureChains.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { ChangeEventHandler, useState } from 'react'; - -import { ChainName } from '@hyperlane-xyz/sdk'; -import { IconButton, Modal, XIcon } from '@hyperlane-xyz/widgets'; - -import { CopyButton } from '../../components/buttons/CopyButton'; -import { SolidButton } from '../../components/buttons/SolidButton'; -import { ChainLogo } from '../../components/icons/ChainLogo'; -import { Card } from '../../components/layout/Card'; -import { docLinks } from '../../consts/links'; -import { useMultiProvider } from '../../store'; - -import { tryParseChainConfig } from './chainConfig'; -import { useChainConfigsRW } from './useChainConfigs'; - -export function ConfigureChains() { - const { chainConfigs, setChainConfigs } = useChainConfigsRW(); - const multiProvider = useMultiProvider(); - - const [showAddChainModal, setShowAddChainModal] = useState(false); - - const [customChainInput, setCustomChainInput] = useState(''); - const onCustomChainInputChange: ChangeEventHandler = (e) => { - setCustomChainInput(e?.target?.value || ''); - }; - const [chainInputErr, setChainInputErr] = useState(''); - - const closeModal = () => { - setShowAddChainModal(false); - setChainInputErr(''); - }; - - const onClickAddChain = () => { - setChainInputErr(''); - const result = tryParseChainConfig(customChainInput, multiProvider); - if (result.success) { - setChainConfigs({ - ...chainConfigs, - [result.chainConfig.name]: result.chainConfig, - }); - setCustomChainInput(''); - setShowAddChainModal(false); - } else { - setChainInputErr(`Invalid config: ${result.error}`); - } - }; - - const onClickRemoveChain = (chainName: ChainName) => { - const newChainConfigs = { ...chainConfigs }; - delete newChainConfigs[chainName]; - setChainConfigs({ - ...newChainConfigs, - }); - }; - - return ( - -

Chain Settings

-

- Hyperlane can be deployed to any chain using{' '} - - Permissionless Interoperability (PI) - - . This explorer can be configured to search for messages on any PI chain. -

-

- To make your chain available to all users, add its metadata to the{' '} - - canonical Hyperlane Registry - - . Or use the section below to add it for just your own use. -

-

Custom Chains

- - - - - - - - - - - - - - {Object.values(chainConfigs).map((chain) => ( - - - - - - - - - - ))} - -
ChainChain IDDomain IDName
- - {chain.chainId}{chain.domainId || chain.chainId}{chain.displayName || chain.name} - onClickRemoveChain(chain.name)} title="Remove"> - - -
- setShowAddChainModal(true)}> - Add custom chain - - -

- Input a chain metadata config including core contract addresses to enable exploration of - that chain. See{' '} - - PI Explorer documentation - {' '} - for examples. -

-
- - -
- {chainInputErr &&
{chainInputErr}
} - - Add - -
-
- ); -} - -const customChainTextareaPlaceholder = `--- -chainId: 11155111 -name: sepolia -protocol: ethereum -rpcUrls: - - http: https://foobar.com -blockExplorers: - - name: Sepolia Etherscan - family: etherscan - url: https://sepolia.etherscan.io - apiUrl: https://api-sepolia.etherscan.io/api - apiKey: '12345' -blocks: - confirmations: 1 - estimateBlockTime: 13 -mailbox: 0x123... -`; - -const styles = { - header: 'pt-2 pb-1 text-sm text-gray-700 font-normal text-left', - value: 'py-4 px-1 text-sm font-light', - valueTruncated: 'py-4 text-sm font-light truncate', -}; diff --git a/src/features/chains/chainConfig.ts b/src/features/chains/chainConfig.ts deleted file mode 100644 index c2bb5b9..0000000 --- a/src/features/chains/chainConfig.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { parse as yamlParse } from 'yaml'; -import { z } from 'zod'; - -import { ChainMetadata, ChainMetadataSchemaObject, MultiProvider } from '@hyperlane-xyz/sdk'; - -import { logger } from '../../utils/logger'; - -export const ChainConfigSchema = ChainMetadataSchemaObject.extend({ - mailbox: z.string().optional(), - interchainGasPaymaster: z.string().optional(), -}); - -export type ChainConfig = ChainMetadata & { mailbox?: Address; interchainGasPaymaster?: Address }; - -type ParseResult = - | { - success: true; - chainConfig: ChainConfig; - } - | { - success: false; - error: string; - }; - -export function tryParseChainConfig(input: string, mp?: MultiProvider): ParseResult { - let data: any; - try { - if (input.startsWith('{')) { - data = JSON.parse(input); - } else { - data = yamlParse(input); - } - } catch (error) { - logger.error('Error parsing chain config', error); - return { - success: false, - error: 'Input is not valid chain JSON or YAML', - }; - } - - const result = ChainConfigSchema.safeParse(data); - - if (!result.success) { - logger.error('Error validating chain config', result.error); - const firstIssue = result.error.issues[0]; - return { - success: false, - error: `${firstIssue.path} => ${firstIssue.message}`, - }; - } - - const chainConfig = result.data as ChainConfig; - - // Ensure https is used for RPCs - const rpcUrls = chainConfig.rpcUrls; - if (rpcUrls?.some((r) => !r.http.startsWith('https://') && !r.http.includes('localhost'))) { - return { - success: false, - error: 'all RPCs must use valid https url', - }; - } - - // Force blockExplorers family value for now - const blockExplorers = chainConfig.blockExplorers; - if (blockExplorers?.some((e) => !e.family)) { - return { - success: false, - error: 'family field for block explorers must be "etherscan"', - }; - } - - // Reject blockscout explorers for now - if (blockExplorers?.[0]?.url.includes('blockscout')) { - return { - success: false, - error: 'only Etherscan-based explorers are supported at this time', - }; - } - - if ( - mp && - (mp.tryGetChainMetadata(chainConfig.name) || - mp.tryGetChainMetadata(chainConfig.chainId) || - (chainConfig.domainId && mp.tryGetChainMetadata(chainConfig.domainId))) - ) { - return { - success: false, - error: 'chainId, domainId, or name is already in use', - }; - } - - return { - success: true, - chainConfig, - }; -} diff --git a/src/features/chains/chainconfig.test.ts b/src/features/chains/chainconfig.test.ts deleted file mode 100644 index 9527cc5..0000000 --- a/src/features/chains/chainconfig.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ChainMetadata, ExplorerFamily } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; - -import { tryParseChainConfig } from './chainConfig'; - -const validConfig: ChainMetadata<{ mailbox: Address }> = { - chainId: 12345, - name: 'mytestnet', - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://fakerpc.com' }], - blockExplorers: [ - { - name: 'FakeScan', - family: ExplorerFamily.Other, - url: 'https://fakeexplorer.com', - apiUrl: 'https://fakeexplorer.com', - }, - ], - blocks: { confirmations: 1, estimateBlockTime: 10 }, - mailbox: '0x14999bccB37118713891DAAA1D5959a02E206C1f', -}; - -describe('chain configs', () => { - it('parses valid config', async () => { - const result = tryParseChainConfig(JSON.stringify(validConfig)); - expect(result.success).toBe(true); - }); - it('rejects invalid config', async () => { - const result = tryParseChainConfig(JSON.stringify({ ...validConfig, chainId: undefined })); - expect(result.success).toBe(false); - }); -}); diff --git a/src/features/chains/queries/useScrapedChains.ts b/src/features/chains/queries/useScrapedChains.ts index 51283a9..7ac7a15 100644 --- a/src/features/chains/queries/useScrapedChains.ts +++ b/src/features/chains/queries/useScrapedChains.ts @@ -36,12 +36,13 @@ export function useScrapedChains() { export function useScrapedEvmChains(multiProvider: MultiProvider) { const { scrapedChains, isFetching, isError } = useScrapedChains(); + const chainMetadata = useStore((s) => s.chainMetadata); const { chains } = useMemo(() => { // Filtering to EVM is necessary to prevent errors until cosmos support is added // https://github.com/hyperlane-xyz/hyperlane-explorer/issues/61 const scrapedEvmChains = objFilter( - multiProvider.metadata, + chainMetadata, (_, chainMetadata): chainMetadata is ChainMetadata => isEvmChain(multiProvider, chainMetadata.chainId) && !isPiChain(multiProvider, scrapedChains, chainMetadata.chainId) && diff --git a/src/features/chains/useChainConfigs.ts b/src/features/chains/useChainMetadata.ts similarity index 58% rename from src/features/chains/useChainConfigs.ts rename to src/features/chains/useChainMetadata.ts index b1422e5..e0338a2 100644 --- a/src/features/chains/useChainConfigs.ts +++ b/src/features/chains/useChainMetadata.ts @@ -1,36 +1,29 @@ import { useEffect } from 'react'; import { z } from 'zod'; -import { ChainMap, ChainMetadata, ChainMetadataSchema } from '@hyperlane-xyz/sdk'; -import { fromBase64, objMerge } from '@hyperlane-xyz/utils'; +import { + ChainMap, + ChainMetadata, + ChainMetadataSchema, + mergeChainMetadataMap, +} from '@hyperlane-xyz/sdk'; +import { fromBase64 } from '@hyperlane-xyz/utils'; import { useStore } from '../../store'; import { logger } from '../../utils/logger'; import { useQueryParam } from '../../utils/queryParams'; -import { ChainConfig } from './chainConfig'; - const CHAIN_CONFIGS_KEY = 'chains'; const ChainMetadataArraySchema = z.array(ChainMetadataSchema); -// Use the chainConfigs from the store -export function useChainConfigs() { - return useStore((s) => s.chainConfigs); -} - -// Use the chainConfigs and setChainConfigs from the store (i.e. Read/Write) -export function useChainConfigsRW() { - return useStore((s) => ({ - chainConfigs: s.chainConfigs, - setChainConfigs: s.setChainConfigs, - })); -} - -// Look for chainConfigs in the query string and merge them into the store +// Look for chainMetadata in the query string and merge them into the store // Not to be used directly, should only require a single use in ChainConfigSyncer export function useQueryParamChainConfigSync() { - const { chainConfigs: storeConfigs, setChainConfigs } = useChainConfigsRW(); + const { chainMetadataOverrides, setChainMetadataOverrides } = useStore((s) => ({ + chainMetadataOverrides: s.chainMetadataOverrides, + setChainMetadataOverrides: s.setChainMetadataOverrides, + })); const queryVal = useQueryParam(CHAIN_CONFIGS_KEY); useEffect(() => { @@ -48,9 +41,13 @@ export function useQueryParamChainConfigSync() { const chainMetadataList = result.data as ChainMetadata[]; // Stop here if there are no new configs to save, otherwise the effect will loop - if (!chainMetadataList.length || chainMetadataList.every((c) => !!storeConfigs[c.name])) return; + if ( + !chainMetadataList.length || + chainMetadataList.every((c) => !!chainMetadataOverrides[c.name]) + ) + return; - const nameToChainConfig = chainMetadataList.reduce>( + const nameToChainConfig = chainMetadataList.reduce>( (acc, chainMetadata) => { // TODO would be great if we could get contract addrs here too // But would require apps like warp template to get that from devs @@ -60,9 +57,9 @@ export function useQueryParamChainConfigSync() { {}, ); - const mergedConfig = objMerge(nameToChainConfig, storeConfigs) as ChainMap; - setChainConfigs(mergedConfig); - }, [storeConfigs, setChainConfigs, queryVal]); + const mergedConfig = mergeChainMetadataMap(nameToChainConfig, chainMetadataOverrides); + setChainMetadataOverrides(mergedConfig); + }, [chainMetadataOverrides, setChainMetadataOverrides, queryVal]); - return storeConfigs; + return chainMetadataOverrides; } diff --git a/src/features/chains/utils.ts b/src/features/chains/utils.ts index 7f97cba..ba4f380 100644 --- a/src/features/chains/utils.ts +++ b/src/features/chains/utils.ts @@ -1,18 +1,17 @@ import { IRegistry } from '@hyperlane-xyz/registry'; -import { ChainMap, MultiProvider } from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainMetadata, MultiProvider } from '@hyperlane-xyz/sdk'; import { ProtocolType, toTitleCase } from '@hyperlane-xyz/utils'; import { Environment } from '../../consts/environments'; -import { ChainConfig } from './chainConfig'; import { DomainsEntry } from './queries/fragments'; export async function getMailboxAddress( chainName: string, - customChainConfigs: ChainMap, + overrideChainMetadata: ChainMap>>, registry: IRegistry, ) { - if (customChainConfigs[chainName]?.mailbox) return customChainConfigs[chainName].mailbox; + if (overrideChainMetadata[chainName]?.mailbox) return overrideChainMetadata[chainName].mailbox; const addresses = await registry.getChainAddresses(chainName); if (addresses?.mailbox) return addresses.mailbox; else return undefined; diff --git a/src/features/debugger/debugMessage.ts b/src/features/debugger/debugMessage.ts index 9c15b17..4d1ecf5 100644 --- a/src/features/debugger/debugMessage.ts +++ b/src/features/debugger/debugMessage.ts @@ -10,7 +10,7 @@ import { InterchainGasPaymaster__factory, } from '@hyperlane-xyz/core'; import { IRegistry } from '@hyperlane-xyz/registry'; -import { ChainMap, MAILBOX_VERSION, MultiProvider } from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainMetadata, MAILBOX_VERSION, MultiProvider } from '@hyperlane-xyz/sdk'; import { addressToBytes32, errorToString, @@ -22,7 +22,6 @@ import { import { Message } from '../../types'; import { logger } from '../../utils/logger'; -import type { ChainConfig } from '../chains/chainConfig'; import { getMailboxAddress } from '../chains/utils'; import { isIcaMessage, tryDecodeIcaBody, tryFetchIcaAddress } from '../messages/ica'; @@ -36,7 +35,7 @@ const IGP_PAYMENT_CHECK_DELAY = 30_000; // 30 seconds export async function debugMessage( multiProvider: MultiProvider, registry: IRegistry, - customChainConfigs: ChainMap, + overrideChainMetadata: ChainMap>, { msgId, nonce, @@ -73,7 +72,7 @@ export async function debugMessage( const recipInvalid = await isInvalidRecipient(destProvider, recipient); if (recipInvalid) return recipInvalid; - const destMailbox = await getMailboxAddress(destName, customChainConfigs, registry); + const destMailbox = await getMailboxAddress(destName, overrideChainMetadata, registry); if (!destMailbox) throw new Error(`Cannot debug message, no mailbox address provided for chain ${destName}`); diff --git a/src/features/deliveryStatus/fetchDeliveryStatus.ts b/src/features/deliveryStatus/fetchDeliveryStatus.ts index 16d1a4f..bd0b035 100644 --- a/src/features/deliveryStatus/fetchDeliveryStatus.ts +++ b/src/features/deliveryStatus/fetchDeliveryStatus.ts @@ -2,13 +2,12 @@ import { constants } from 'ethers'; import { IMailbox__factory } from '@hyperlane-xyz/core'; import { IRegistry } from '@hyperlane-xyz/registry'; -import { ChainMap, MultiProvider } from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainMetadata, MultiProvider } from '@hyperlane-xyz/sdk'; import { DELIVERY_LOG_CHECK_BLOCK_RANGE } from '../../consts/values'; import { Message, MessageStatus } from '../../types'; import { logger } from '../../utils/logger'; import { toDecimalNumber } from '../../utils/number'; -import type { ChainConfig } from '../chains/chainConfig'; import { getMailboxAddress } from '../chains/utils'; import { debugMessage } from '../debugger/debugMessage'; import { MessageDebugStatus } from '../debugger/types'; @@ -23,11 +22,11 @@ import { export async function fetchDeliveryStatus( multiProvider: MultiProvider, registry: IRegistry, - customChainConfigs: ChainMap, + overrideChainMetadata: ChainMap>, message: Message, ): Promise { const destName = multiProvider.getChainName(message.destinationChainId); - const destMailboxAddr = await getMailboxAddress(destName, customChainConfigs, registry); + const destMailboxAddr = await getMailboxAddress(destName, overrideChainMetadata, registry); if (!destMailboxAddr) throw new Error( `Cannot check delivery status, no mailbox address provided for chain ${destName}`, @@ -68,7 +67,7 @@ export async function fetchDeliveryStatus( }; return result; } else { - const debugResult = await debugMessage(multiProvider, registry, customChainConfigs, message); + const debugResult = await debugMessage(multiProvider, registry, overrideChainMetadata, message); const messageStatus = debugResult.status === MessageDebugStatus.NoErrorsFound ? MessageStatus.Pending diff --git a/src/features/deliveryStatus/useMessageDeliveryStatus.tsx b/src/features/deliveryStatus/useMessageDeliveryStatus.tsx index b1d1f09..de36f29 100644 --- a/src/features/deliveryStatus/useMessageDeliveryStatus.tsx +++ b/src/features/deliveryStatus/useMessageDeliveryStatus.tsx @@ -5,11 +5,10 @@ import { toast } from 'react-toastify'; import { MultiProvider } from '@hyperlane-xyz/sdk'; import { errorToString } from '@hyperlane-xyz/utils'; -import { useReadyMultiProvider, useRegistry } from '../../store'; +import { useReadyMultiProvider, useRegistry, useStore } from '../../store'; import { Message, MessageStatus } from '../../types'; import { logger } from '../../utils/logger'; import { MissingChainConfigToast } from '../chains/MissingChainConfigToast'; -import { useChainConfigs } from '../chains/useChainConfigs'; import { isEvmChain } from '../chains/utils'; import { fetchDeliveryStatus } from './fetchDeliveryStatus'; @@ -21,7 +20,7 @@ export function useMessageDeliveryStatus({ message: Message; enabled: boolean; }) { - const chainConfigs = useChainConfigs(); + const chainMetadataOverrides = useStore((s) => s.chainMetadataOverrides) || {}; const multiProvider = useReadyMultiProvider(); const registry = useRegistry(); @@ -46,7 +45,7 @@ export function useMessageDeliveryStatus({ const deliverStatus = await fetchDeliveryStatus( multiProvider, registry, - chainConfigs, + chainMetadataOverrides, message, ); diff --git a/src/features/messages/pi-queries/fetchPiChainMessages.test.ts b/src/features/messages/pi-queries/fetchPiChainMessages.test.ts index 3849859..0aec8a4 100644 --- a/src/features/messages/pi-queries/fetchPiChainMessages.test.ts +++ b/src/features/messages/pi-queries/fetchPiChainMessages.test.ts @@ -1,9 +1,8 @@ import { GithubRegistry, chainAddresses, chainMetadata } from '@hyperlane-xyz/registry'; -import { MultiProvider } from '@hyperlane-xyz/sdk'; +import { ChainMetadata, MultiProvider } from '@hyperlane-xyz/sdk'; import { config } from '../../../consts/config'; import { Message, MessageStatus } from '../../../types'; -import { ChainConfig } from '../../chains/chainConfig'; import { fetchMessagesFromPiChain } from './fetchPiChainMessages'; @@ -15,7 +14,10 @@ jest.setTimeout(30000); const sepoliaMailbox = chainAddresses.sepolia.mailbox; const sepoliaIgp = chainAddresses.sepolia.interchainGasPaymaster; -const sepoliaConfigWithExplorer: ChainConfig = { +const sepoliaConfigWithExplorer: ChainMetadata<{ + mailbox: string; + interchainGasPaymaster: string; +}> = { ...chainMetadata.sepolia, mailbox: sepoliaMailbox, interchainGasPaymaster: sepoliaIgp, @@ -165,6 +167,6 @@ describe('fetchMessagesFromPiChain', () => { }); }); -function createMP(config: ChainConfig) { +function createMP(config: ChainMetadata) { return new MultiProvider({ ...chainMetadata, sepolia: config }); } diff --git a/src/features/messages/pi-queries/fetchPiChainMessages.ts b/src/features/messages/pi-queries/fetchPiChainMessages.ts index 2c430ef..68ade64 100644 --- a/src/features/messages/pi-queries/fetchPiChainMessages.ts +++ b/src/features/messages/pi-queries/fetchPiChainMessages.ts @@ -2,7 +2,7 @@ import { BigNumber, constants, ethers, providers } from 'ethers'; import { IInterchainGasPaymaster__factory, Mailbox__factory } from '@hyperlane-xyz/core'; import { IRegistry } from '@hyperlane-xyz/registry'; -import { MultiProvider } from '@hyperlane-xyz/sdk'; +import { ChainMetadata, MultiProvider } from '@hyperlane-xyz/sdk'; import { ProtocolType, addressToBytes32, @@ -17,7 +17,6 @@ import { import { PI_MESSAGE_LOG_CHECK_BLOCK_RANGE } from '../../../consts/values'; import { ExtendedLog, Message, MessageStatus } from '../../../types'; import { logger } from '../../../utils/logger'; -import { ChainConfig } from '../../chains/chainConfig'; const mailbox = Mailbox__factory.createInterface(); const dispatchTopic0 = mailbox.getEventTopic('Dispatch'); @@ -65,7 +64,7 @@ searchForMessages(input): */ export async function fetchMessagesFromPiChain( - chainConfig: ChainConfig, + chainMetadata: ChainMetadata, query: PiMessageQuery, multiProvider: MultiProvider, registry: IRegistry, @@ -75,14 +74,14 @@ export async function fetchMessagesFromPiChain( let logs: ExtendedLog[] = []; if (isValidAddress(input) && (!queryType || queryType === PiQueryType.Address)) { - logs = await fetchLogsForAddress(chainConfig, query, multiProvider, registry); + logs = await fetchLogsForAddress(chainMetadata, query, multiProvider, registry); } else if (isValidTransactionHash(input, ProtocolType.Ethereum)) { if (!queryType || queryType === PiQueryType.TxHash) { - logs = await fetchLogsForTxHash(chainConfig, query, multiProvider); + logs = await fetchLogsForTxHash(chainMetadata, query, multiProvider); } // Input may be a msg id, check that next if ((!queryType || queryType === PiQueryType.MsgId) && !logs.length) { - logs = await fetchLogsForMsgId(chainConfig, query, multiProvider, registry); + logs = await fetchLogsForMsgId(chainMetadata, query, multiProvider, registry); } } else { logger.warn('Invalid PI search input', input, queryType); @@ -90,7 +89,7 @@ export async function fetchMessagesFromPiChain( } const messages = logs - .map((l) => logToMessage(multiProvider, l, chainConfig)) + .map((l) => logToMessage(multiProvider, l, chainMetadata)) .filter((m): m is Message => !!m); // Fetch IGP gas payments for each message if it's a small set @@ -98,7 +97,7 @@ export async function fetchMessagesFromPiChain( const messagesWithGasPayments: Message[] = []; // Avoiding parallelism here out of caution for RPC rate limits for (const m of messages) { - messagesWithGasPayments.push(await tryFetchIgpGasPayments(m, chainConfig, multiProvider)); + messagesWithGasPayments.push(await tryFetchIgpGasPayments(m, chainMetadata, multiProvider)); } return messagesWithGasPayments; } else { @@ -108,15 +107,15 @@ export async function fetchMessagesFromPiChain( } async function fetchLogsForAddress( - chainConfig: ChainConfig, + chainMetadata: ChainMetadata, query: PiMessageQuery, multiProvider: MultiProvider, registry: IRegistry, ): Promise { - const { chainId } = chainConfig; + const { chainId } = chainMetadata; const address = query.input; logger.debug(`Fetching logs for address ${address} on chain ${chainId}`); - const mailbox = await resolveMailbox(chainConfig, multiProvider, registry); + const mailbox = await resolveMailbox(chainMetadata, multiProvider, registry); if (!mailbox) return []; const dispatchTopic = addressToBytes32(address); @@ -135,7 +134,7 @@ async function fetchLogsForAddress( } async function fetchLogsForTxHash( - { chainId }: ChainConfig, + { chainId }: ChainMetadata, query: PiMessageQuery, multiProvider: MultiProvider, ): Promise { @@ -159,15 +158,15 @@ async function fetchLogsForTxHash( } async function fetchLogsForMsgId( - chainConfig: ChainConfig, + chainMetadata: ChainMetadata, query: PiMessageQuery, multiProvider: MultiProvider, registry: IRegistry, ): Promise { - const { chainId } = chainConfig; + const { chainId } = chainMetadata; const msgId = query.input; logger.debug(`Fetching logs for msgId ${msgId} on chain ${chainId}`); - const mailbox = await resolveMailbox(chainConfig, multiProvider, registry); + const mailbox = await resolveMailbox(chainMetadata, multiProvider, registry); if (!mailbox) return []; const topic1 = msgId; const logs: ExtendedLog[] = await fetchLogsFromProvider( @@ -186,7 +185,7 @@ async function fetchLogsForMsgId( if (logs.length) { const txHash = logs[0].transactionHash; logger.debug('Found tx hash with log with msg id. Hash:', txHash); - return fetchLogsForTxHash(chainConfig, { ...query, input: txHash }, multiProvider) || []; + return fetchLogsForTxHash(chainMetadata, { ...query, input: txHash }, multiProvider) || []; } return []; @@ -252,7 +251,7 @@ function parseBlockTimestamp(block: providers.Block | null | undefined): number function logToMessage( multiProvider: MultiProvider, log: ExtendedLog, - chainConfig: ChainConfig, + chainMetadata: ChainMetadata<{ mailbox?: Address }>, ): Message | null { let logDesc: ethers.utils.LogDescription; try { @@ -292,7 +291,7 @@ function logToMessage( to: log.to ? normalizeAddress(log.to) : constants.AddressZero, blockHash: log.blockHash, blockNumber: BigNumber.from(log.blockNumber).toNumber(), - mailbox: chainConfig.mailbox || constants.AddressZero, + mailbox: chainMetadata.mailbox || constants.AddressZero, nonce: 0, // TODO get more gas info from tx gasLimit: 0, @@ -314,10 +313,10 @@ function logToMessage( // Fetch and sum all IGP gas payments for a given message async function tryFetchIgpGasPayments( message: Message, - chainConfig: ChainConfig, + chainMetadata: ChainMetadata<{ interchainGasPaymaster?: Address }>, multiProvider: MultiProvider, ): Promise { - const { chainId, interchainGasPaymaster } = chainConfig; + const { chainId, interchainGasPaymaster } = chainMetadata; if (!interchainGasPaymaster || !isValidAddress(interchainGasPaymaster)) { logger.warn('No IGP address found for chain:', chainId); return message; @@ -346,12 +345,12 @@ async function tryFetchIgpGasPayments( } async function resolveMailbox( - chainConfig: ChainConfig, + chainMetadata: ChainMetadata<{ mailbox?: Address }>, multiProvider: MultiProvider, registry: IRegistry, ) { - if (chainConfig.mailbox) return chainConfig.mailbox; - const chainName = multiProvider.getChainName(chainConfig.chainId); + if (chainMetadata.mailbox) return chainMetadata.mailbox; + const chainName = multiProvider.getChainName(chainMetadata.chainId); const chainAddresses = await registry.getChainAddresses(chainName); const mailbox = chainAddresses?.mailbox; if (!mailbox) logger.debug(`No mailbox address found for chain ${chainName}`); diff --git a/src/features/messages/pi-queries/usePiChainMessageQuery.ts b/src/features/messages/pi-queries/usePiChainMessageQuery.ts index 5452662..36ad163 100644 --- a/src/features/messages/pi-queries/usePiChainMessageQuery.ts +++ b/src/features/messages/pi-queries/usePiChainMessageQuery.ts @@ -1,13 +1,12 @@ import { useQuery } from '@tanstack/react-query'; import { IRegistry } from '@hyperlane-xyz/registry'; -import { MultiProvider } from '@hyperlane-xyz/sdk'; +import { ChainMetadata, MultiProvider } from '@hyperlane-xyz/sdk'; import { ensure0x, timeout } from '@hyperlane-xyz/utils'; import { useReadyMultiProvider, useRegistry } from '../../../store'; import { Message } from '../../../types'; import { logger } from '../../../utils/logger'; -import { ChainConfig } from '../../chains/chainConfig'; import { useScrapedChains } from '../../chains/queries/useScrapedChains'; import { isEvmChain, isPiChain } from '../../chains/utils'; import { isValidSearchQuery } from '../queries/useMessageQuery'; @@ -16,8 +15,8 @@ import { PiMessageQuery, PiQueryType, fetchMessagesFromPiChain } from './fetchPi const MESSAGE_SEARCH_TIMEOUT = 10_000; // 10s -// Query 'Permissionless Interoperability (PI)' chains using custom -// chain configs in store state +// Query 'Permissionless Interoperability (PI)' chains using +// override chain metadata in store state export function usePiChainMessageSearchQuery({ sanitizedInput, startTimeFilter, @@ -113,7 +112,7 @@ export function usePiChainMessageQuery({ } async function fetchMessages( - chainConfig: ChainConfig, + chainMetadata: ChainMetadata, query: PiMessageQuery, multiProvider: MultiProvider, registry: IRegistry, @@ -122,13 +121,13 @@ async function fetchMessages( let messages: Message[]; try { messages = await timeout( - fetchMessagesFromPiChain(chainConfig, query, multiProvider, registry, queryType), + fetchMessagesFromPiChain(chainMetadata, query, multiProvider, registry, queryType), MESSAGE_SEARCH_TIMEOUT, 'message search timeout', ); return messages; } catch (error) { - logger.debug('Error fetching PI messages for chain:', chainConfig.name, error); + logger.debug('Error fetching PI messages for chain:', chainMetadata.name, error); throw error; } } diff --git a/src/images/backgrounds/footer-line-desktop.svg b/src/images/backgrounds/footer-line-desktop.svg deleted file mode 100644 index be04d27..0000000 --- a/src/images/backgrounds/footer-line-desktop.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/images/backgrounds/footer-line-mobile.svg b/src/images/backgrounds/footer-line-mobile.svg deleted file mode 100644 index 73ec163..0000000 --- a/src/images/backgrounds/footer-line-mobile.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/multiProvider.ts b/src/multiProvider.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx deleted file mode 100644 index bba117a..0000000 --- a/src/pages/settings.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { NextPage } from 'next'; - -import { ConfigureChains } from '../features/chains/ConfigureChains'; - -const SettingsPage: NextPage = () => { - return ( -
- -
- ); -}; - -export default SettingsPage; diff --git a/src/store.ts b/src/store.ts index 9e2e476..0f996da 100644 --- a/src/store.ts +++ b/src/store.ts @@ -2,24 +2,25 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { GithubRegistry, IRegistry } from '@hyperlane-xyz/registry'; -import { ChainMap, ChainMetadata, MultiProvider } from '@hyperlane-xyz/sdk'; -import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; +import { ChainMap, ChainMetadata, MultiProvider, mergeChainMetadataMap } from '@hyperlane-xyz/sdk'; +import { objFilter, objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { config } from './consts/config'; -import { ChainConfig } from './features/chains/chainConfig'; import { DomainsEntry } from './features/chains/queries/fragments'; import { logger } from './utils/logger'; // Increment this when persist state has breaking changes -const PERSIST_STATE_VERSION = 1; +const PERSIST_STATE_VERSION = 2; // Keeping everything here for now as state is simple // Will refactor into slices as necessary interface AppState { scrapedChains: Array; setScrapedChains: (chains: Array) => void; - chainConfigs: ChainMap; - setChainConfigs: (configs: ChainMap) => void; + chainMetadata: ChainMap; + setChainMetadata: (metadata: ChainMap) => void; + chainMetadataOverrides: ChainMap>; + setChainMetadataOverrides: (overrides?: ChainMap | undefined>) => void; multiProvider: MultiProvider; setMultiProvider: (mp: MultiProvider) => void; registry: IRegistry; @@ -33,13 +34,20 @@ export const useStore = create()( (set, get) => ({ scrapedChains: [], setScrapedChains: (chains: Array) => set({ scrapedChains: chains }), - chainConfigs: {}, - setChainConfigs: async (configs: ChainMap) => { - const multiProvider = await buildMultiProvider(get().registry, configs); - set({ chainConfigs: configs, multiProvider }); + chainMetadata: {}, + setChainMetadata: (metadata: ChainMap) => set({ chainMetadata: metadata }), + chainMetadataOverrides: {}, + setChainMetadataOverrides: async ( + overrides: ChainMap | undefined> = {}, + ) => { + logger.debug('Setting chain overrides in store'); + const { multiProvider } = await buildMultiProvider(get().registry, overrides); + const filtered = objFilter(overrides, (_, metadata) => !!metadata); + set({ chainMetadataOverrides: filtered, multiProvider }); }, multiProvider: new MultiProvider({}), setMultiProvider: (multiProvider: MultiProvider) => { + logger.debug('Setting multiProvider in store'); set({ multiProvider }); }, registry: new GithubRegistry({ proxyUrl: config.githubProxy }), @@ -52,7 +60,7 @@ export const useStore = create()( { name: 'hyperlane', // name in storage version: PERSIST_STATE_VERSION, - partialize: (state) => ({ chainConfigs: state.chainConfigs }), // fields to persist + partialize: (state) => ({ chainMetadataOverrides: state.chainMetadataOverrides }), // fields to persist onRehydrateStorage: () => { logger.debug('Rehydrating state'); return (state, error) => { @@ -60,9 +68,10 @@ export const useStore = create()( logger.error('Error during hydration', error); return; } - buildMultiProvider(state.registry, state.chainConfigs) - .then((mp) => { - state.setMultiProvider(mp); + buildMultiProvider(state.registry, state.chainMetadataOverrides) + .then(({ metadata, multiProvider }) => { + state.setChainMetadata(metadata); + state.setMultiProvider(multiProvider); logger.debug('Rehydration complete'); }) .catch((e) => logger.error('Error building MultiProvider', e)); @@ -84,11 +93,15 @@ export function useMultiProvider() { // otherwise returns undefined export function useReadyMultiProvider() { const multiProvider = useMultiProvider(); - if (multiProvider.getKnownChainNames().length === 0) return undefined; + if (!multiProvider.getKnownChainNames().length) return undefined; return multiProvider; } -async function buildMultiProvider(registry: IRegistry, customChainConfigs: ChainMap) { +async function buildMultiProvider( + registry: IRegistry, + overrideChainMetadata: ChainMap | undefined>, +) { + logger.debug('Building new MultiProvider from registry'); // TODO improve interface so this pre-cache isn't required await registry.listRegistryContent(); const registryChainMetadata = await registry.getMetadata(); @@ -102,5 +115,6 @@ async function buildMultiProvider(registry: IRegistry, customChainConfigs: Chain }), ), ); - return new MultiProvider({ ...metadataWithLogos, ...customChainConfigs }); + const mergedMetadata = mergeChainMetadataMap(metadataWithLogos, overrideChainMetadata); + return { metadata: metadataWithLogos, multiProvider: new MultiProvider(mergedMetadata) }; } From c1accbe2fe3986c17d123025b9b4631af3128eb3 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 7 Oct 2024 16:23:12 -0400 Subject: [PATCH 08/14] Update hyp packages to 5.4.0-beta0 --- package.json | 8 +++--- yarn.lock | 75 ++++++++++++++++++++++++++++------------------------ 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 858e646..11ea25f 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "@hyperlane-xyz/explorer", "description": "An interchain explorer for the Hyperlane protocol and network.", - "version": "5.3.0", + "version": "5.4.0", "author": "J M Rossy", "dependencies": { "@headlessui/react": "^2.1.8", "@hyperlane-xyz/registry": "4.4.1", - "@hyperlane-xyz/sdk": "5.3.0", - "@hyperlane-xyz/utils": "5.3.0", - "@hyperlane-xyz/widgets": "5.3.0", + "@hyperlane-xyz/sdk": "5.4.0-beta.0", + "@hyperlane-xyz/utils": "5.4.0-beta.0", + "@hyperlane-xyz/widgets": "5.4.0-beta.0", "@tanstack/react-query": "^5.35.5", "bignumber.js": "^9.1.2", "buffer": "^6.0.3", diff --git a/yarn.lock b/yarn.lock index 7fc31bb..bff4ffb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2150,13 +2150,13 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/core@npm:5.3.0": - version: 5.3.0 - resolution: "@hyperlane-xyz/core@npm:5.3.0" +"@hyperlane-xyz/core@npm:5.4.0-beta.0": + version: 5.4.0-beta.0 + resolution: "@hyperlane-xyz/core@npm:5.4.0-beta.0" dependencies: "@arbitrum/nitro-contracts": "npm:^1.2.1" "@eth-optimism/contracts": "npm:^0.6.0" - "@hyperlane-xyz/utils": "npm:5.3.0" + "@hyperlane-xyz/utils": "npm:5.4.0-beta.0" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@openzeppelin/contracts": "npm:^4.9.3" "@openzeppelin/contracts-upgradeable": "npm:^v4.9.3" @@ -2165,7 +2165,7 @@ __metadata: "@ethersproject/abi": "*" "@ethersproject/providers": "*" "@types/sinon-chai": "*" - checksum: f3d4ca187d59cb3f7f6c7767a7f4204b5ee17b9593b1f36272cbbaf34261b3055f5d51f55ecc590591b817b8f3a4c2aa8b63af9aee16864519df50c6faf5707a + checksum: 47faf2f25ac6be739e7e81670d4d8ba3743ba9b9dabd0f08d47ce02a2007ddf0cb9fb547bd1e6076dfee5129fa7c14054aa51c83cdb6f115c7c7e4c1749ab71b languageName: node linkType: hard @@ -2175,9 +2175,9 @@ __metadata: dependencies: "@headlessui/react": "npm:^2.1.8" "@hyperlane-xyz/registry": "npm:4.4.1" - "@hyperlane-xyz/sdk": "npm:5.3.0" - "@hyperlane-xyz/utils": "npm:5.3.0" - "@hyperlane-xyz/widgets": "npm:5.3.0" + "@hyperlane-xyz/sdk": "npm:5.4.0-beta.0" + "@hyperlane-xyz/utils": "npm:5.4.0-beta.0" + "@hyperlane-xyz/widgets": "npm:5.4.0-beta.0" "@tanstack/react-query": "npm:^5.35.5" "@trivago/prettier-plugin-sort-imports": "npm:^4.1.1" "@types/jest": "npm:^29.5.3" @@ -2215,16 +2215,6 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/registry@npm:4.3.6": - version: 4.3.6 - resolution: "@hyperlane-xyz/registry@npm:4.3.6" - dependencies: - yaml: "npm:2.4.5" - zod: "npm:^3.21.2" - checksum: 7cc42813f4f8b8ef09266be249f3dcec0584832166419df2f48eec3cc43ba766e58845ecc16673bf6465a711f08ff6c4fc5216da2f704bc31ef8ade52af4b6e5 - languageName: node - linkType: hard - "@hyperlane-xyz/registry@npm:4.4.1": version: 4.4.1 resolution: "@hyperlane-xyz/registry@npm:4.4.1" @@ -2235,16 +2225,16 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:5.3.0": - version: 5.3.0 - resolution: "@hyperlane-xyz/sdk@npm:5.3.0" +"@hyperlane-xyz/sdk@npm:5.4.0-beta.0": + version: 5.4.0-beta.0 + resolution: "@hyperlane-xyz/sdk@npm:5.4.0-beta.0" dependencies: "@arbitrum/sdk": "npm:^4.0.0" "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/cosmwasm-stargate": "npm:^0.32.4" "@cosmjs/stargate": "npm:^0.32.4" - "@hyperlane-xyz/core": "npm:5.3.0" - "@hyperlane-xyz/utils": "npm:5.3.0" + "@hyperlane-xyz/core": "npm:5.4.0-beta.0" + "@hyperlane-xyz/utils": "npm:5.4.0-beta.0" "@safe-global/api-kit": "npm:1.3.0" "@safe-global/protocol-kit": "npm:1.3.0" "@safe-global/safe-deployments": "npm:1.37.8" @@ -2263,13 +2253,13 @@ __metadata: peerDependencies: "@ethersproject/abi": "*" "@ethersproject/providers": "*" - checksum: f72eb29ddceff027c9c6a2a85679aaca0beff94ea171cc5653bc3f57e53350e69ba5ab11484b9fff3e3f764a9cbcf51db0fa3ab71fbe92707e2dc209e61af3e6 + checksum: b1c4c7f4a52dc8804f9ddd1d60497b942c75ee2641d8adfaa97ad26a958c7d06e2e19bc411d205ca34dcb2e89f067f095e52e2b1374a8abb04539a14ce6699fa languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:5.3.0": - version: 5.3.0 - resolution: "@hyperlane-xyz/utils@npm:5.3.0" +"@hyperlane-xyz/utils@npm:5.4.0-beta.0": + version: 5.4.0-beta.0 + resolution: "@hyperlane-xyz/utils@npm:5.4.0-beta.0" dependencies: "@cosmjs/encoding": "npm:^0.32.4" "@solana/web3.js": "npm:^1.78.0" @@ -2278,20 +2268,24 @@ __metadata: lodash-es: "npm:^4.17.21" pino: "npm:^8.19.0" yaml: "npm:2.4.5" - checksum: 2ae64331824f21c8741c2e94eadd1e863c5ce6f0b4d84e35b03eeae47b2473403636eda0f5aa648523a312b605de2a69fb9fff73949e26e3410e85b4c2fe0d27 + checksum: 89810da17e558988caae6c4bf2f8bebaa3886b2775e5c890c6e1d10cab10b3611dd884ef9bccf385da5d3101b8bc98611f29cf50cbff3dbbc7ec1b7b070d8d28 languageName: node linkType: hard -"@hyperlane-xyz/widgets@npm:5.3.0": - version: 5.3.0 - resolution: "@hyperlane-xyz/widgets@npm:5.3.0" +"@hyperlane-xyz/widgets@npm:5.4.0-beta.0": + version: 5.4.0-beta.0 + resolution: "@hyperlane-xyz/widgets@npm:5.4.0-beta.0" dependencies: - "@hyperlane-xyz/registry": "npm:4.3.6" - "@hyperlane-xyz/sdk": "npm:5.3.0" + "@headlessui/react": "npm:^2.1.8" + "@hyperlane-xyz/sdk": "npm:5.4.0-beta.0" + "@hyperlane-xyz/utils": "npm:5.4.0-beta.0" + clsx: "npm:^2.1.1" + react-tooltip: "npm:^5.28.0" + zustand: "npm:^4.5.5" peerDependencies: react: ^18 react-dom: ^18 - checksum: 231d03f88451dc0b0defa1002c6f0da058fb999a75b80457d7be9e1f6b381bc6e5ba9745aab54f56d3efb3be07682eb58301fa6c61b32ba2b8920aa46b6aa5d5 + checksum: 87b60c76991163faf8346ba98cbbf6167b6d71d4174875ae2a2308db2e31be652bda3bb5f9b5e2a8f9b4a8960b45c46a28113d22a1a0b6feba76d5d46fc770e1 languageName: node linkType: hard @@ -11162,6 +11156,19 @@ __metadata: languageName: node linkType: hard +"react-tooltip@npm:^5.28.0": + version: 5.28.0 + resolution: "react-tooltip@npm:5.28.0" + dependencies: + "@floating-ui/dom": "npm:^1.6.1" + classnames: "npm:^2.3.0" + peerDependencies: + react: ">=16.14.0" + react-dom: ">=16.14.0" + checksum: ec13ad0fafcae51c9c1193c6f0bccba4e7047e9d02eaf77231474cefd1a3d05254e76f27229808e79dad4c0a8c47b8e5cafdad47920e34a11d7a2703adf5f998 + languageName: node + linkType: hard + "react@npm:^18.2.0": version: 18.2.0 resolution: "react@npm:18.2.0" From 774f856a3fd9df00f4910875895e9f323014a86e Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 7 Oct 2024 16:38:18 -0400 Subject: [PATCH 09/14] Shorten header animation --- src/components/nav/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/nav/Header.tsx b/src/components/nav/Header.tsx index bc12d2d..3244a34 100644 --- a/src/components/nav/Header.tsx +++ b/src/components/nav/Header.tsx @@ -25,7 +25,7 @@ export function Header({ pathName }: { pathName: string }) { return (
From 92eba0107c0d3ff5b27f5ab4431b7ee84b1943c5 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 7 Oct 2024 16:47:11 -0400 Subject: [PATCH 10/14] Prevent header animation in firefox Fix mini search bar icon color --- src/components/search/MiniSearchBar.tsx | 4 +++- src/utils/browser.ts | 3 +++ src/utils/useScrollListener.ts | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 src/utils/browser.ts diff --git a/src/components/search/MiniSearchBar.tsx b/src/components/search/MiniSearchBar.tsx index b204204..ce05181 100644 --- a/src/components/search/MiniSearchBar.tsx +++ b/src/components/search/MiniSearchBar.tsx @@ -3,6 +3,8 @@ import { useRouter } from 'next/router'; import { IconButton, SearchIcon } from '@hyperlane-xyz/widgets'; +import { Color } from '../../styles/Color'; + interface FormValues { search: string; } @@ -31,7 +33,7 @@ export function MiniSearchBar() { />
- +
diff --git a/src/utils/browser.ts b/src/utils/browser.ts new file mode 100644 index 0000000..c0a393f --- /dev/null +++ b/src/utils/browser.ts @@ -0,0 +1,3 @@ +export function isFirefox() { + return typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().includes('firefox'); +} diff --git a/src/utils/useScrollListener.ts b/src/utils/useScrollListener.ts index fb8b9bd..a23981a 100644 --- a/src/utils/useScrollListener.ts +++ b/src/utils/useScrollListener.ts @@ -1,5 +1,7 @@ import { useEffect, useState } from 'react'; +import { isFirefox } from './browser'; + export function useScrollThresholdListener(threshold: number, debounce = 500) { const [isAboveThreshold, setIsAbove] = useState(false); const [isDebouncing, setIsDebouncing] = useState(false); @@ -8,6 +10,9 @@ export function useScrollThresholdListener(threshold: number, debounce = 500) { let timeoutId: NodeJS.Timeout | null; const listener = () => { + // TODO find a way to make this animation smooth in Firefox + if (isFirefox()) return; + const handleScroll = () => { if (window.scrollY > threshold && !isAboveThreshold) { setIsAbove(true); From c03a94c7468244453b82e739066f77f755903b58 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 8 Oct 2024 11:12:54 -0400 Subject: [PATCH 11/14] Disable focus-visible outline by default --- src/styles/global.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles/global.css b/src/styles/global.css index 4ec9c68..1dccd44 100755 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -34,6 +34,10 @@ select:focus { outline: none; } +:focus-visible { + outline: none; +} + /* Background ========== From bc6f4f623523fed2449fb302775b0400670bbfa3 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 9 Oct 2024 11:23:53 -0400 Subject: [PATCH 12/14] Update footer and fix z-index bug --- src/components/nav/Footer.tsx | 115 +++++++++++++++------------------- 1 file changed, 51 insertions(+), 64 deletions(-) diff --git a/src/components/nav/Footer.tsx b/src/components/nav/Footer.tsx index 479c1fe..92e7a01 100644 --- a/src/components/nav/Footer.tsx +++ b/src/components/nav/Footer.tsx @@ -1,12 +1,9 @@ // Partly copied from https://github.com/hyperlane-xyz/hyperlane-website/blob/main/src/components/nav/Footer.tsx -import Image from 'next/image'; import Link from 'next/link'; import { HyperlaneLogo } from '@hyperlane-xyz/widgets'; import { docLinks, links } from '../../consts/links'; -import FooterBg from '../../images/backgrounds/footer-bg.svg'; -import FooterTopBorder from '../../images/backgrounds/footer-top-border.svg'; import { Color } from '../../styles/Color'; import { Discord } from '../icons/Discord'; import { Github } from '../icons/Github'; @@ -36,69 +33,59 @@ const footerLinks3 = [ export function Footer() { return ( -