diff --git a/src/Address.tsx b/src/Address.tsx index 07883a5..06fd7c7 100644 --- a/src/Address.tsx +++ b/src/Address.tsx @@ -1,183 +1,28 @@ -import React, { useEffect, useContext, useCallback } from "react"; -import { - useParams, - useNavigate, - Routes, - Route, - useSearchParams, -} from "react-router-dom"; -import { Tab } from "@headlessui/react"; -import Blockies from "react-blockies"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch"; -import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons/faQuestionCircle"; -import StandardFrame from "./StandardFrame"; -import StandardSubtitle from "./StandardSubtitle"; -import AddressOrENSNameNotFound from "./components/AddressOrENSNameNotFound"; -import Copy from "./components/Copy"; -import Faucet from "./components/Faucet"; -import NavTab from "./components/NavTab"; -import SourcifyLogo from "./sourcify/SourcifyLogo"; -import AddressTransactionResults from "./address/AddressTransactionResults"; -import Contracts from "./address/Contracts"; -import { RuntimeContext } from "./useRuntime"; -import { useAddressOrENS } from "./useResolvedAddresses"; -import { useSourcifyMetadata } from "./sourcify/useSourcify"; -import { ChecksummedAddress } from "./types"; -import { useHasCode } from "./useErigonHooks"; -import { useChainInfo } from "./useChainInfo"; +import React from "react"; +import { useSearchParams } from "react-router-dom"; +import AddressMainPage from "./AddressMainPage"; const AddressTransactionByNonce = React.lazy( () => import("./AddressTransactionByNonce") ); +/** + * This is the default handler for /address/* URL path. + * + * It can redirect to different child components depending on search + * query params, so it is not possible to use default path routing + * mechanisms to declarative-model them. + */ const Address: React.FC = () => { - const { provider } = useContext(RuntimeContext); - const { addressOrName, direction } = useParams(); - if (addressOrName === undefined) { - throw new Error("addressOrName couldn't be undefined here"); - } - - const navigate = useNavigate(); - const [searchParams] = useSearchParams(); - const urlFixer = useCallback( - (address: ChecksummedAddress) => { - navigate( - `/address/${address}${ - direction ? "/" + direction : "" - }?${searchParams.toString()}`, - { replace: true } - ); - }, - [navigate, direction, searchParams] - ); - const [checksummedAddress, isENS, error] = useAddressOrENS( - addressOrName, - urlFixer - ); - - useEffect(() => { - if (isENS || checksummedAddress === undefined) { - document.title = `Address ${addressOrName} | Otterscan`; - } else { - document.title = `Address ${checksummedAddress} | Otterscan`; - } - }, [addressOrName, checksummedAddress, isENS]); - - const hasCode = useHasCode(provider, checksummedAddress, "latest"); - const addressMetadata = useSourcifyMetadata( - hasCode ? checksummedAddress : undefined, - provider?.network.chainId - ); - - const { network, faucets } = useChainInfo(); - // Search address by nonce === transaction @ nonce + const [searchParams] = useSearchParams(); const rawNonce = searchParams.get("nonce"); if (rawNonce !== null) { - return ( - - ); + return ; } - return ( - - {error ? ( - - ) : ( - checksummedAddress && ( - <> - -
- - Address - - {checksummedAddress} - - - {/* Only display faucets for testnets who actually have any */} - {network === "testnet" && faucets && faucets.length > 0 && ( - - )} - {isENS && ( - - ENS: {addressOrName} - - )} -
-
- - - Overview - {hasCode && ( - - - Contract - {addressMetadata === undefined ? ( - - - - ) : addressMetadata === null ? ( - - - - ) : ( - - - - )} - - - )} - - - - - } - /> - - } - /> - - } - /> - - - - - ) - )} -
- ); + // Standard address main page with tabs + return ; }; export default Address; diff --git a/src/AddressMainPage.tsx b/src/AddressMainPage.tsx new file mode 100644 index 0000000..77a3e80 --- /dev/null +++ b/src/AddressMainPage.tsx @@ -0,0 +1,170 @@ +import React, { useEffect, useCallback, useContext } from "react"; +import { + Routes, + Route, + useNavigate, + useParams, + useSearchParams, +} from "react-router-dom"; +import { Tab } from "@headlessui/react"; +import Blockies from "react-blockies"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch"; +import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons/faQuestionCircle"; +import StandardFrame from "./StandardFrame"; +import StandardSubtitle from "./StandardSubtitle"; +import AddressOrENSNameNotFound from "./components/AddressOrENSNameNotFound"; +import Copy from "./components/Copy"; +import Faucet from "./components/Faucet"; +import NavTab from "./components/NavTab"; +import SourcifyLogo from "./sourcify/SourcifyLogo"; +import AddressTransactionResults from "./address/AddressTransactionResults"; +import Contracts from "./address/Contracts"; +import { RuntimeContext } from "./useRuntime"; +import { useHasCode } from "./useErigonHooks"; +import { useChainInfo } from "./useChainInfo"; +import { useAddressOrENS } from "./useResolvedAddresses"; +import { useSourcifyMetadata } from "./sourcify/useSourcify"; +import { ChecksummedAddress } from "./types"; + +type AddressMainPageProps = {}; + +const AddressMainPage: React.FC = ({}) => { + const { addressOrName, direction } = useParams(); + if (addressOrName === undefined) { + throw new Error("addressOrName couldn't be undefined here"); + } + + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const urlFixer = useCallback( + (address: ChecksummedAddress) => { + navigate( + `/address/${address}${ + direction ? "/" + direction : "" + }?${searchParams.toString()}`, + { replace: true } + ); + }, + [navigate, direction, searchParams] + ); + const [checksummedAddress, isENS, error] = useAddressOrENS( + addressOrName, + urlFixer + ); + + const { provider } = useContext(RuntimeContext); + const hasCode = useHasCode(provider, checksummedAddress, "latest"); + const addressMetadata = useSourcifyMetadata( + hasCode ? checksummedAddress : undefined, + provider?.network.chainId + ); + + const { network, faucets } = useChainInfo(); + + useEffect(() => { + if (isENS || checksummedAddress === undefined) { + document.title = `Address ${addressOrName} | Otterscan`; + } else { + document.title = `Address ${checksummedAddress} | Otterscan`; + } + }, [addressOrName, checksummedAddress, isENS]); + + return ( + + {error ? ( + + ) : ( + checksummedAddress && ( + <> + +
+ + Address + + {checksummedAddress} + + + {/* Only display faucets for testnets who actually have any */} + {network === "testnet" && faucets && faucets.length > 0 && ( + + )} + {isENS && ( + + ENS: {addressOrName} + + )} +
+
+ + + Overview + {hasCode && ( + + + Contract + {addressMetadata === undefined ? ( + + + + ) : addressMetadata === null ? ( + + + + ) : ( + + + + )} + + + )} + + + + + } + /> + + } + /> + + } + /> + + + + + ) + )} +
+ ); +}; + +export default AddressMainPage; diff --git a/src/AddressTransactionByNonce.tsx b/src/AddressTransactionByNonce.tsx index e804f84..8e4cc2e 100644 --- a/src/AddressTransactionByNonce.tsx +++ b/src/AddressTransactionByNonce.tsx @@ -1,24 +1,47 @@ -import React, { useContext, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import React, { useCallback, useContext, useEffect, useState } from "react"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import StandardFrame from "./StandardFrame"; +import AddressOrENSNameNotFound from "./components/AddressOrENSNameNotFound"; import AddressOrENSNameInvalidNonce from "./components/AddressOrENSNameInvalidNonce"; import AddressOrENSNameNoTx from "./components/AddressOrENSNameNoTx"; -import { ChecksummedAddress } from "./types"; -import { transactionURL } from "./url"; import { useTransactionBySenderAndNonce } from "./useErigonHooks"; import { RuntimeContext } from "./useRuntime"; +import { useAddressOrENS } from "./useResolvedAddresses"; +import { ChecksummedAddress } from "./types"; +import { transactionURL } from "./url"; type AddressTransactionByNonceProps = { - checksummedAddress: ChecksummedAddress | undefined; rawNonce: string; }; const AddressTransactionByNonce: React.FC = ({ - checksummedAddress, rawNonce, }) => { const { provider } = useContext(RuntimeContext); + const { addressOrName, direction } = useParams(); + if (addressOrName === undefined) { + throw new Error("addressOrName couldn't be undefined here"); + } + + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const urlFixer = useCallback( + (address: ChecksummedAddress) => { + navigate( + `/address/${address}${ + direction ? "/" + direction : "" + }?${searchParams.toString()}`, + { replace: true } + ); + }, + [navigate, direction, searchParams] + ); + const [checksummedAddress, , ensError] = useAddressOrENS( + addressOrName, + urlFixer + ); + // Calculate txCount ONLY when asked for latest nonce const [txCount, setTxCount] = useState(); useEffect(() => { @@ -54,14 +77,21 @@ const AddressTransactionByNonce: React.FC = ({ checksummedAddress, nonce !== undefined && isNaN(nonce) ? undefined : nonce ); - const navigate = useNavigate(); + + // Invalid ENS + if (ensError) { + return ( + + + + ); + } // Loading... - if ( - checksummedAddress === undefined || - nonce === undefined || - txHash === undefined - ) { + if (checksummedAddress === undefined || nonce === undefined) { return ; } @@ -86,6 +116,11 @@ const AddressTransactionByNonce: React.FC = ({ ); } + // Valid nonce, waiting tx load + if (txHash === undefined) { + return ; + } + // Valid nonce, but no tx found if (txHash === null) { return (