Add deep-linking and query param updating for debugger

pull/6/head
J M Rossy 2 years ago
parent 7340ea589a
commit 820f6cbb28
  1. 48
      src/components/nav/EnvironmentSelector.tsx
  2. 31
      src/components/nav/Header.tsx
  3. 2
      src/consts/environments.ts
  4. 17
      src/features/debugger/TxDebugger.tsx
  5. 3
      src/pages/_app.tsx
  6. 7
      src/pages/debugger.tsx
  7. 7
      src/store.ts
  8. 28
      src/utils/queryParams.ts

@ -0,0 +1,48 @@
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { Environment, envDisplayValue, environments } from '../../consts/environments';
import { useEnvironment } from '../../store';
import { getQueryParamString, replacePathParam } from '../../utils/queryParams';
import { SelectField } from '../input/SelectField';
export function EnvironmentSelector() {
const router = useRouter();
const { environment, setEnvironment } = useEnvironment();
const queryEnv = getQueryParamString(router.query, 'env');
useEffect(() => {
if (!queryEnv || queryEnv === environment) return;
if (environments.includes(queryEnv as Environment)) {
setEnvironment(queryEnv as Environment);
}
// special case to alias testnet as testnet2
if (queryEnv.toLowerCase() === 'testnet') {
setEnvironment(Environment.Testnet2);
}
// Only run once on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onSelect = (env: string) => {
setEnvironment(env as Environment);
replacePathParam('env', env);
};
return (
<div className="relative">
{/* <Image src={HubIcon} width={20} height={20} className="opacity-70" /> */}
<SelectField
classes="w-24 text-gray-600 border-gray-600 bg-gray-50 text-[0.95rem]"
options={envOptions}
value={environment}
onValueSelect={onSelect}
/>
</div>
);
}
const envOptions = [
{ value: Environment.Mainnet, display: envDisplayValue[Environment.Mainnet] },
{ value: Environment.Testnet2, display: envDisplayValue[Environment.Testnet2] },
];

@ -2,7 +2,6 @@ import Image from 'next/future/image';
import Link from 'next/link'; import Link from 'next/link';
import useDropdownMenu from 'react-accessible-dropdown-menu-hook'; import useDropdownMenu from 'react-accessible-dropdown-menu-hook';
import { Environment, envDisplayValue } from '../../consts/environments';
import { links } from '../../consts/links'; import { links } from '../../consts/links';
import BookIcon from '../../images/icons/book.svg'; import BookIcon from '../../images/icons/book.svg';
import BugIcon from '../../images/icons/bug.svg'; import BugIcon from '../../images/icons/bug.svg';
@ -11,8 +10,8 @@ import HouseIcon from '../../images/icons/house.svg';
import InfoIcon from '../../images/icons/info-circle.svg'; import InfoIcon from '../../images/icons/info-circle.svg';
import Logo from '../../images/logos/hyperlane-logo.svg'; import Logo from '../../images/logos/hyperlane-logo.svg';
import Name from '../../images/logos/hyperlane-name.svg'; import Name from '../../images/logos/hyperlane-name.svg';
import { useStore } from '../../store';
import { SelectField } from '../input/SelectField'; import { EnvironmentSelector } from './EnvironmentSelector';
export function Header({ pathName }: { pathName: string }) { export function Header({ pathName }: { pathName: string }) {
const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(4); const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(4);
@ -44,7 +43,7 @@ export function Header({ pathName }: { pathName: string }) {
<a className={styles.navLink} target="_blank" href={links.home} rel="noopener noreferrer"> <a className={styles.navLink} target="_blank" href={links.home} rel="noopener noreferrer">
About About
</a> </a>
<NetworkSelector /> <EnvironmentSelector />
</nav> </nav>
<div className="relative flex item-center sm:hidden mr-2"> <div className="relative flex item-center sm:hidden mr-2">
<button className="hover:opactiy-70 transition-all" {...buttonProps}> <button className="hover:opactiy-70 transition-all" {...buttonProps}>
@ -98,30 +97,6 @@ function DropdownItemContent({ icon, text }: { icon: any; text: string }) {
); );
} }
function NetworkSelector() {
const { environment, setEnvironment } = useStore((s) => ({
environment: s.environment,
setEnvironment: s.setEnvironment,
}));
return (
<div className="relative">
{/* <Image src={HubIcon} width={20} height={20} className="opacity-70" /> */}
<SelectField
classes="w-24 text-gray-600 border-gray-600 text-[0.95rem]"
options={envOptions}
value={environment}
onValueSelect={(e: string) => setEnvironment(e as Environment)}
/>
</div>
);
}
const envOptions = [
{ value: Environment.Mainnet, display: envDisplayValue[Environment.Mainnet] },
{ value: Environment.Testnet2, display: envDisplayValue[Environment.Testnet2] },
];
const styles = { const styles = {
navLink: navLink:
'flex items-center tracking-wide text-gray-600 text-[0.95rem] hover:underline hover:opacity-70 decoration-2 underline-offset-[6px] transition-all', 'flex items-center tracking-wide text-gray-600 text-[0.95rem] hover:underline hover:opacity-70 decoration-2 underline-offset-[6px] transition-all',

@ -3,6 +3,8 @@ export enum Environment {
Testnet2 = 'testnet2', Testnet2 = 'testnet2',
} }
export const environments = Object.values(Environment);
export const envDisplayValue = { export const envDisplayValue = {
[Environment.Mainnet]: 'Mainnet', [Environment.Mainnet]: 'Mainnet',
[Environment.Testnet2]: 'Testnet', [Environment.Testnet2]: 'Testnet',

@ -1,5 +1,6 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import Image from 'next/future/image'; import Image from 'next/future/image';
import { useRouter } from 'next/router';
import { useState } from 'react'; import { useState } from 'react';
import { Fade } from '../../components/animation/Fade'; import { Fade } from '../../components/animation/Fade';
@ -14,6 +15,7 @@ import { envDisplayValue } from '../../consts/environments';
import ShrugIcon from '../../images/icons/shrug.svg'; import ShrugIcon from '../../images/icons/shrug.svg';
import { useStore } from '../../store'; import { useStore } from '../../store';
import useDebounce from '../../utils/debounce'; import useDebounce from '../../utils/debounce';
import { getQueryParamString, replacePathParam } from '../../utils/queryParams';
import { sanitizeString, toTitleCase } from '../../utils/string'; import { sanitizeString, toTitleCase } from '../../utils/string';
import { isValidSearchQuery } from '../search/utils'; import { isValidSearchQuery } from '../search/utils';
@ -22,8 +24,11 @@ import { MessageDebugResult, TxDebugStatus, debugMessageForHash } from './debugM
export function TxDebugger() { export function TxDebugger() {
const environment = useStore((s) => s.environment); const environment = useStore((s) => s.environment);
const router = useRouter();
const txHash = getQueryParamString(router.query, 'txHash');
// Search text input // Search text input
const [searchInput, setSearchInput] = useState(''); const [searchInput, setSearchInput] = useState(txHash);
const debouncedSearchInput = useDebounce(searchInput, 750); const debouncedSearchInput = useDebounce(searchInput, 750);
const hasInput = !!debouncedSearchInput; const hasInput = !!debouncedSearchInput;
const sanitizedInput = sanitizeString(debouncedSearchInput); const sanitizedInput = sanitizeString(debouncedSearchInput);
@ -35,9 +40,13 @@ export function TxDebugger() {
data, data,
} = useQuery( } = useQuery(
['debugMessage', isValidInput, sanitizedInput, environment], ['debugMessage', isValidInput, sanitizedInput, environment],
() => { async () => {
if (!isValidInput || !sanitizedInput) return null; if (!isValidInput || !sanitizedInput) {
else return debugMessageForHash(sanitizedInput, environment); replacePathParam('txHash', '');
return null;
}
replacePathParam('txHash', sanitizedInput);
return debugMessageForHash(sanitizedInput, environment);
}, },
{ retry: false }, { retry: false },
); );

@ -65,7 +65,6 @@ export default function App({ Component, router, pageProps }: AppProps) {
return <div></div>; return <div></div>;
} }
const pathName = router.pathname;
return ( return (
<ErrorBoundary> <ErrorBoundary>
<WagmiConfig client={wagmiClient}> <WagmiConfig client={wagmiClient}>
@ -79,7 +78,7 @@ export default function App({ Component, router, pageProps }: AppProps) {
> >
<QueryClientProvider client={reactQueryClient}> <QueryClientProvider client={reactQueryClient}>
<UrqlProvider value={urqlClients[environment]}> <UrqlProvider value={urqlClients[environment]}>
<AppLayout pathName={pathName}> <AppLayout pathName={router.pathname}>
<Component {...pageProps} /> <Component {...pageProps} />
</AppLayout> </AppLayout>
</UrqlProvider> </UrqlProvider>

@ -11,4 +11,11 @@ const Debugger: NextPage = () => {
); );
}; };
// Required for dynamic routing to work by disabling Automatic Static Optimization
export async function getServerSideProps() {
return {
props: {},
};
}
export default Debugger; export default Debugger;

@ -17,3 +17,10 @@ export const useStore = create<AppState>()((set) => ({
bannerClassName: '', bannerClassName: '',
setBanner: (className: string) => set(() => ({ bannerClassName: className })), setBanner: (className: string) => set(() => ({ bannerClassName: className })),
})); }));
export function useEnvironment() {
return useStore((s) => ({
environment: s.environment,
setEnvironment: s.setEnvironment,
}));
}

@ -0,0 +1,28 @@
import type { ParsedUrlQuery } from 'querystring';
import { logger } from './logger';
// To make Next's awkward query param typing more convenient
export function getQueryParamString(query: ParsedUrlQuery, key: string, defaultVal = '') {
if (!query) return defaultVal;
const val = query[key];
if (val && typeof val === 'string') return val;
else return defaultVal;
}
// Circumventing Next's router.replace method here because
// it's async and causes race conditions btwn components.
// This will only modify the url but not trigger any routing
export function replacePathParam(key: string, val: string) {
try {
const url = new URL(window.location.href);
if (val) {
url.searchParams.set(key, val);
} else {
url.searchParams.delete(key);
}
window.history.replaceState('', '', url);
} catch (error) {
logger.error('Error replacing path param', error);
}
}
Loading…
Cancel
Save