diff --git a/explorer/src/App.tsx b/explorer/src/App.tsx index 1b700ce35c..ebbfde1fe7 100644 --- a/explorer/src/App.tsx +++ b/explorer/src/App.tsx @@ -1,18 +1,18 @@ import React from "react"; import { Switch, Route, Redirect } from "react-router-dom"; -import AccountDetails from "./components/AccountDetails"; -import TransactionDetails from "./components/TransactionDetails"; -import ClusterModal from "./components/ClusterModal"; -import { TX_ALIASES } from "./providers/transactions"; -import TopAccountsCard from "components/TopAccountsCard"; -import SupplyCard from "components/SupplyCard"; -import StatsCard from "components/StatsCard"; -import MessageBanner from "components/MessageBanner"; -import Navbar from "components/Navbar"; +import { ClusterModal } from "components/ClusterModal"; +import { TX_ALIASES } from "providers/transactions"; +import { MessageBanner } from "components/MessageBanner"; +import { Navbar } from "components/Navbar"; import { ClusterStatusBanner } from "components/ClusterStatusButton"; import { SearchBar } from "components/SearchBar"; +import { AccountDetailsPage } from "pages/AccountDetailsPage"; +import { ClusterStatsPage } from "pages/ClusterStatsPage"; +import { SupplyPage } from "pages/SupplyPage"; +import { TransactionDetailsPage } from "pages/TransactionDetailsPage"; + const ACCOUNT_ALIASES = ["account", "accounts", "addresses"]; function App() { @@ -26,10 +26,7 @@ function App() { -
- - -
+
`/${tx}/:signature` )} render={({ match }) => ( - + )} /> ( - )} /> -
- -
+
( diff --git a/explorer/src/components/AccountDetails.tsx b/explorer/src/components/AccountDetails.tsx deleted file mode 100644 index 8e342fb96e..0000000000 --- a/explorer/src/components/AccountDetails.tsx +++ /dev/null @@ -1,411 +0,0 @@ -import React from "react"; -import { PublicKey, StakeProgram } from "@solana/web3.js"; -import { - FetchStatus, - useFetchAccountInfo, - useAccountInfo, - useAccountHistory, - Account, -} from "providers/accounts"; -import { lamportsToSolString } from "utils"; -import { StakeAccountCards } from "components/account/StakeAccountCards"; -import ErrorCard from "components/common/ErrorCard"; -import LoadingCard from "components/common/LoadingCard"; -import TableCardBody from "components/common/TableCardBody"; -import { useFetchAccountHistory } from "providers/accounts/history"; -import { - useFetchAccountOwnedTokens, - useAccountOwnedTokens, - TokenAccountData, -} from "providers/accounts/tokens"; -import { useCluster, ClusterStatus } from "providers/cluster"; -import Address from "./common/Address"; -import Signature from "./common/Signature"; -import { NavLink } from "react-router-dom"; -import { clusterPath } from "utils/url"; - -type Props = { address: string; tab?: string }; -export default function AccountDetails({ address, tab }: Props) { - let pubkey: PublicKey | undefined; - try { - pubkey = new PublicKey(address); - } catch (err) { - console.error(err); - // TODO handle bad addresses - } - - let moreTab: MoreTabs = "history"; - if (tab === "history" || tab === "tokens") { - moreTab = tab; - } - - return ( -
-
-
-
Details
-

Account

-
-
- {pubkey && } - {pubkey && } -
- ); -} - -type MoreTabs = "history" | "tokens"; -function MoreSection({ pubkey, tab }: { pubkey: PublicKey; tab: MoreTabs }) { - const address = pubkey.toBase58(); - const info = useAccountInfo(address); - if (!info || info.lamports === undefined) return null; - - return ( - <> -
-
-
-
    -
  • - - History - -
  • -
  • - - Tokens - -
  • -
-
-
-
- {tab === "tokens" && } - {tab === "history" && } - - ); -} - -function AccountCards({ pubkey }: { pubkey: PublicKey }) { - const fetchAccount = useFetchAccountInfo(); - const address = pubkey.toBase58(); - const info = useAccountInfo(address); - const refresh = useFetchAccountInfo(); - const { status } = useCluster(); - - // Fetch account on load - React.useEffect(() => { - if (!info && status === ClusterStatus.Connected) fetchAccount(pubkey); - }, [address, status]); // eslint-disable-line react-hooks/exhaustive-deps - - if (!info || info.status === FetchStatus.Fetching) { - return ; - } else if ( - info.status === FetchStatus.FetchFailed || - info.lamports === undefined - ) { - return refresh(pubkey)} text="Fetch Failed" />; - } - - const owner = info.details?.owner; - const data = info.details?.data; - if (data && owner && owner.equals(StakeProgram.programId)) { - return ; - } else { - return ; - } -} - -function UnknownAccountCard({ account }: { account: Account }) { - const { details, lamports } = account; - if (lamports === undefined) return null; - - return ( -
-
-

Overview

-
- - - - Address - -
- - - - Balance (SOL) - - {lamportsToSolString(lamports)} - - - - {details && ( - - Data (Bytes) - {details.space} - - )} - - {details && ( - - Owner - -
- - - )} - - {details && ( - - Executable - - {details.executable ? "Yes" : "No"} - - - )} - -
- ); -} - -function TokensCard({ pubkey }: { pubkey: PublicKey }) { - const address = pubkey.toBase58(); - const ownedTokens = useAccountOwnedTokens(address); - const fetchAccountTokens = useFetchAccountOwnedTokens(); - const refresh = () => fetchAccountTokens(pubkey); - - // Fetch owned tokens - React.useEffect(() => { - if (!ownedTokens) refresh(); - }, [address]); // eslint-disable-line react-hooks/exhaustive-deps - - if (ownedTokens === undefined) { - return null; - } - - const { status, tokens } = ownedTokens; - const fetching = status === FetchStatus.Fetching; - if (fetching && (tokens === undefined || tokens.length === 0)) { - return ; - } else if (tokens === undefined) { - return ; - } - - if (tokens.length === 0) { - return ( - - ); - } - - const mappedTokens = new Map(); - for (const token of tokens) { - const mintAddress = token.mint.toBase58(); - const tokenInfo = mappedTokens.get(mintAddress); - if (tokenInfo) { - tokenInfo.amount += token.amount; - } else { - mappedTokens.set(mintAddress, token); - } - } - - const detailsList: React.ReactNode[] = []; - mappedTokens.forEach((tokenInfo, mintAddress) => { - const balance = tokenInfo.amount; - detailsList.push( - - -
- - {balance} - - ); - }); - - return ( -
-
-

Owned Tokens

- -
- -
- - - - - - - - {detailsList} -
Token AddressBalance
-
-
- ); -} - -function HistoryCard({ pubkey }: { pubkey: PublicKey }) { - const address = pubkey.toBase58(); - const info = useAccountInfo(address); - const history = useAccountHistory(address); - const fetchAccountHistory = useFetchAccountHistory(); - const refresh = () => fetchAccountHistory(pubkey, true); - const loadMore = () => fetchAccountHistory(pubkey); - - React.useEffect(() => { - if (!history) refresh(); - }, [address]); // eslint-disable-line react-hooks/exhaustive-deps - - if (!info || !history || info.lamports === undefined) { - return null; - } else if (history.fetched === undefined) { - if (history.status === FetchStatus.Fetching) { - return ; - } - - return ( - - ); - } - - if (history.fetched.length === 0) { - if (history.status === FetchStatus.Fetching) { - return ; - } - return ( - - ); - } - - const detailsList: React.ReactNode[] = []; - const transactions = history.fetched; - - for (var i = 0; i < transactions.length; i++) { - const slot = transactions[i].slot; - const slotTransactions = [transactions[i]]; - while (i + 1 < transactions.length) { - const nextSlot = transactions[i + 1].slot; - if (nextSlot !== slot) break; - slotTransactions.push(transactions[++i]); - } - - slotTransactions.forEach(({ signature, err }) => { - let statusText; - let statusClass; - if (err) { - statusClass = "warning"; - statusText = "Failed"; - } else { - statusClass = "success"; - statusText = "Success"; - } - - detailsList.push( - - {slot} - - - - {statusText} - - - - - - - - ); - }); - } - - const fetching = history.status === FetchStatus.Fetching; - return ( -
-
-

Transaction History

- -
- -
- - - - - - - - - {detailsList} -
SlotResultTransaction Signature
-
- -
- {history.foundOldest ? ( -
Fetched full history
- ) : ( - - )} -
-
- ); -} diff --git a/explorer/src/components/ClusterModal.tsx b/explorer/src/components/ClusterModal.tsx index 74eef136c5..ddbc6cb9e5 100644 --- a/explorer/src/components/ClusterModal.tsx +++ b/explorer/src/components/ClusterModal.tsx @@ -12,12 +12,12 @@ import { Cluster, useClusterModal, useUpdateCustomUrl, -} from "../providers/cluster"; +} from "providers/cluster"; import { assertUnreachable } from "../utils"; -import Overlay from "./Overlay"; +import { Overlay } from "./common/Overlay"; import { useQuery } from "utils/url"; -function ClusterModal() { +export function ClusterModal() { const [show, setShow] = useClusterModal(); const onClose = () => setShow(false); return ( @@ -164,5 +164,3 @@ function ClusterToggle() { ); } - -export default ClusterModal; diff --git a/explorer/src/components/ClusterStatusButton.tsx b/explorer/src/components/ClusterStatusButton.tsx index 216cf315da..c7efc7ccad 100644 --- a/explorer/src/components/ClusterStatusButton.tsx +++ b/explorer/src/components/ClusterStatusButton.tsx @@ -4,7 +4,7 @@ import { ClusterStatus, Cluster, useClusterModal, -} from "../providers/cluster"; +} from "providers/cluster"; export function ClusterStatusBanner() { const [, setShow] = useClusterModal(); diff --git a/explorer/src/components/MessageBanner.tsx b/explorer/src/components/MessageBanner.tsx index 698f07a8b5..19b22df924 100644 --- a/explorer/src/components/MessageBanner.tsx +++ b/explorer/src/components/MessageBanner.tsx @@ -25,7 +25,7 @@ const announcements = new Map(); // "Mainnet Beta upgrade in progress. Transactions disabled until epoch 62", // }); -export default function Banner() { +export function MessageBanner() { const cluster = useCluster().cluster; const announcement = announcements.get(cluster); if (!announcement) return null; diff --git a/explorer/src/components/Navbar.tsx b/explorer/src/components/Navbar.tsx index 64eecb1ad2..e8de5ef6ed 100644 --- a/explorer/src/components/Navbar.tsx +++ b/explorer/src/components/Navbar.tsx @@ -4,7 +4,7 @@ import { clusterPath } from "utils/url"; import { Link, NavLink } from "react-router-dom"; import { ClusterStatusButton } from "components/ClusterStatusButton"; -export default function Navbar() { +export function Navbar() { // TODO: use `collapsing` to animate collapsible navbar const [collapse, setCollapse] = React.useState(false); diff --git a/explorer/src/components/SupplyCard.tsx b/explorer/src/components/SupplyCard.tsx index eaa747966b..0250f22dad 100644 --- a/explorer/src/components/SupplyCard.tsx +++ b/explorer/src/components/SupplyCard.tsx @@ -1,11 +1,11 @@ import React from "react"; import { useSupply, useFetchSupply, Status } from "providers/supply"; -import LoadingCard from "./common/LoadingCard"; -import ErrorCard from "./common/ErrorCard"; +import { LoadingCard } from "./common/LoadingCard"; +import { ErrorCard } from "./common/ErrorCard"; import { lamportsToSolString } from "utils"; -import TableCardBody from "./common/TableCardBody"; +import { TableCardBody } from "./common/TableCardBody"; -export default function SupplyCard() { +export function SupplyCard() { const supply = useSupply(); const fetchSupply = useFetchSupply(); diff --git a/explorer/src/components/TopAccountsCard.tsx b/explorer/src/components/TopAccountsCard.tsx index a4093bb3ef..2d0d713a11 100644 --- a/explorer/src/components/TopAccountsCard.tsx +++ b/explorer/src/components/TopAccountsCard.tsx @@ -3,16 +3,16 @@ import { Link } from "react-router-dom"; import { Location } from "history"; import { AccountBalancePair } from "@solana/web3.js"; import { useRichList, useFetchRichList, Status } from "providers/richList"; -import LoadingCard from "./common/LoadingCard"; -import ErrorCard from "./common/ErrorCard"; +import { LoadingCard } from "./common/LoadingCard"; +import { ErrorCard } from "./common/ErrorCard"; import { lamportsToSolString } from "utils"; import { useQuery } from "utils/url"; import { useSupply } from "providers/supply"; -import Address from "./common/Address"; +import { Address } from "./common/Address"; type Filter = "circulating" | "nonCirculating" | "all" | null; -export default function TopAccountsCard() { +export function TopAccountsCard() { const supply = useSupply(); const richList = useRichList(); const fetchRichList = useFetchRichList(); diff --git a/explorer/src/components/account/OwnedTokensCard.tsx b/explorer/src/components/account/OwnedTokensCard.tsx new file mode 100644 index 0000000000..a7444b55c3 --- /dev/null +++ b/explorer/src/components/account/OwnedTokensCard.tsx @@ -0,0 +1,106 @@ +import React from "react"; +import { PublicKey } from "@solana/web3.js"; +import { FetchStatus } from "providers/accounts"; +import { + useFetchAccountOwnedTokens, + useAccountOwnedTokens, + TokenAccountData, +} from "providers/accounts/tokens"; +import { ErrorCard } from "components/common/ErrorCard"; +import { LoadingCard } from "components/common/LoadingCard"; +import { Address } from "components/common/Address"; + +export function OwnedTokensCard({ pubkey }: { pubkey: PublicKey }) { + const address = pubkey.toBase58(); + const ownedTokens = useAccountOwnedTokens(address); + const fetchAccountTokens = useFetchAccountOwnedTokens(); + const refresh = () => fetchAccountTokens(pubkey); + + // Fetch owned tokens + React.useEffect(() => { + if (!ownedTokens) refresh(); + }, [address]); // eslint-disable-line react-hooks/exhaustive-deps + + if (ownedTokens === undefined) { + return null; + } + + const { status, tokens } = ownedTokens; + const fetching = status === FetchStatus.Fetching; + if (fetching && (tokens === undefined || tokens.length === 0)) { + return ; + } else if (tokens === undefined) { + return ; + } + + if (tokens.length === 0) { + return ( + + ); + } + + const mappedTokens = new Map(); + for (const token of tokens) { + const mintAddress = token.mint.toBase58(); + const tokenInfo = mappedTokens.get(mintAddress); + if (tokenInfo) { + tokenInfo.amount += token.amount; + } else { + mappedTokens.set(mintAddress, token); + } + } + + const detailsList: React.ReactNode[] = []; + mappedTokens.forEach((tokenInfo, mintAddress) => { + const balance = tokenInfo.amount; + detailsList.push( + + +
+ + {balance} + + ); + }); + + return ( +
+
+

Owned Tokens

+ +
+ +
+ + + + + + + + {detailsList} +
Token AddressBalance
+
+
+ ); +} diff --git a/explorer/src/components/account/StakeAccountCards.tsx b/explorer/src/components/account/StakeAccountSection.tsx similarity index 97% rename from explorer/src/components/account/StakeAccountCards.tsx rename to explorer/src/components/account/StakeAccountSection.tsx index b83dd187ee..3352ba8bda 100644 --- a/explorer/src/components/account/StakeAccountCards.tsx +++ b/explorer/src/components/account/StakeAccountSection.tsx @@ -1,12 +1,12 @@ import React from "react"; import { StakeAccount, Meta } from "solana-sdk-wasm"; -import TableCardBody from "components/common/TableCardBody"; +import { TableCardBody } from "components/common/TableCardBody"; import { lamportsToSolString } from "utils"; import { displayTimestamp } from "utils/date"; import { Account, useFetchAccountInfo } from "providers/accounts"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; -export function StakeAccountCards({ +export function StakeAccountSection({ account, stakeAccount, }: { diff --git a/explorer/src/components/account/TransactionHistoryCard.tsx b/explorer/src/components/account/TransactionHistoryCard.tsx new file mode 100644 index 0000000000..81c2ba8a87 --- /dev/null +++ b/explorer/src/components/account/TransactionHistoryCard.tsx @@ -0,0 +1,150 @@ +import React from "react"; +import { PublicKey } from "@solana/web3.js"; +import { + FetchStatus, + useAccountInfo, + useAccountHistory, +} from "providers/accounts"; +import { useFetchAccountHistory } from "providers/accounts/history"; +import { Signature } from "components/common/Signature"; +import { ErrorCard } from "components/common/ErrorCard"; +import { LoadingCard } from "components/common/LoadingCard"; + +export function TransactionHistoryCard({ pubkey }: { pubkey: PublicKey }) { + const address = pubkey.toBase58(); + const info = useAccountInfo(address); + const history = useAccountHistory(address); + const fetchAccountHistory = useFetchAccountHistory(); + const refresh = () => fetchAccountHistory(pubkey, true); + const loadMore = () => fetchAccountHistory(pubkey); + + React.useEffect(() => { + if (!history) refresh(); + }, [address]); // eslint-disable-line react-hooks/exhaustive-deps + + if (!info || !history || info.lamports === undefined) { + return null; + } else if (history.fetched === undefined) { + if (history.status === FetchStatus.Fetching) { + return ; + } + + return ( + + ); + } + + if (history.fetched.length === 0) { + if (history.status === FetchStatus.Fetching) { + return ; + } + return ( + + ); + } + + const detailsList: React.ReactNode[] = []; + const transactions = history.fetched; + + for (var i = 0; i < transactions.length; i++) { + const slot = transactions[i].slot; + const slotTransactions = [transactions[i]]; + while (i + 1 < transactions.length) { + const nextSlot = transactions[i + 1].slot; + if (nextSlot !== slot) break; + slotTransactions.push(transactions[++i]); + } + + slotTransactions.forEach(({ signature, err }) => { + let statusText; + let statusClass; + if (err) { + statusClass = "warning"; + statusText = "Failed"; + } else { + statusClass = "success"; + statusText = "Success"; + } + + detailsList.push( + + {slot} + + + + {statusText} + + + + + + + + ); + }); + } + + const fetching = history.status === FetchStatus.Fetching; + return ( +
+
+

Transaction History

+ +
+ +
+ + + + + + + + + {detailsList} +
SlotResultTransaction Signature
+
+ +
+ {history.foundOldest ? ( +
Fetched full history
+ ) : ( + + )} +
+
+ ); +} diff --git a/explorer/src/components/account/UnknownAccountCard.tsx b/explorer/src/components/account/UnknownAccountCard.tsx new file mode 100644 index 0000000000..26da019b30 --- /dev/null +++ b/explorer/src/components/account/UnknownAccountCard.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { Account } from "providers/accounts"; +import { lamportsToSolString } from "utils"; +import { TableCardBody } from "components/common/TableCardBody"; +import { Address } from "components/common/Address"; + +export function UnknownAccountCard({ account }: { account: Account }) { + const { details, lamports } = account; + if (lamports === undefined) return null; + + return ( +
+
+

Overview

+
+ + + + Address + +
+ + + + Balance (SOL) + + {lamportsToSolString(lamports)} + + + + {details && ( + + Data (Bytes) + {details.space} + + )} + + {details && ( + + Owner + +
+ + + )} + + {details && ( + + Executable + + {details.executable ? "Yes" : "No"} + + + )} + +
+ ); +} diff --git a/explorer/src/components/common/Address.tsx b/explorer/src/components/common/Address.tsx index e1639d979e..94f2d45849 100644 --- a/explorer/src/components/common/Address.tsx +++ b/explorer/src/components/common/Address.tsx @@ -12,7 +12,7 @@ type Props = { link?: boolean; }; -export default function Address({ pubkey, alignRight, link }: Props) { +export function Address({ pubkey, alignRight, link }: Props) { const [state, setState] = useState("copy"); const address = pubkey.toBase58(); diff --git a/explorer/src/components/Copyable.tsx b/explorer/src/components/common/Copyable.tsx similarity index 92% rename from explorer/src/components/Copyable.tsx rename to explorer/src/components/common/Copyable.tsx index 8d29e26d55..5b7f45ca0d 100644 --- a/explorer/src/components/Copyable.tsx +++ b/explorer/src/components/common/Copyable.tsx @@ -32,7 +32,7 @@ function Popover({ ); } -function Copyable({ bottom, right, text, children }: CopyableProps) { +export function Copyable({ bottom, right, text, children }: CopyableProps) { const [state, setState] = useState("hide"); const copyToClipboard = () => navigator.clipboard.writeText(text); @@ -54,5 +54,3 @@ function Copyable({ bottom, right, text, children }: CopyableProps) { ); } - -export default Copyable; diff --git a/explorer/src/components/common/ErrorCard.tsx b/explorer/src/components/common/ErrorCard.tsx index d6675c277c..fe00535533 100644 --- a/explorer/src/components/common/ErrorCard.tsx +++ b/explorer/src/components/common/ErrorCard.tsx @@ -1,6 +1,6 @@ import React from "react"; -export default function ErrorCard({ +export function ErrorCard({ retry, retryText, text, diff --git a/explorer/src/components/InfoTooltip.tsx b/explorer/src/components/common/InfoTooltip.tsx similarity index 92% rename from explorer/src/components/InfoTooltip.tsx rename to explorer/src/components/common/InfoTooltip.tsx index cc0ea40c82..4baf498889 100644 --- a/explorer/src/components/InfoTooltip.tsx +++ b/explorer/src/components/common/InfoTooltip.tsx @@ -33,7 +33,7 @@ function Popover({ ); } -function InfoTooltip({ bottom, right, text, children }: Props) { +export function InfoTooltip({ bottom, right, text, children }: Props) { const [state, setState] = useState("hide"); const justify = right ? "end" : "start"; @@ -51,5 +51,3 @@ function InfoTooltip({ bottom, right, text, children }: Props) { ); } - -export default InfoTooltip; diff --git a/explorer/src/components/common/LoadingCard.tsx b/explorer/src/components/common/LoadingCard.tsx index c46348db7d..738cdf9c0f 100644 --- a/explorer/src/components/common/LoadingCard.tsx +++ b/explorer/src/components/common/LoadingCard.tsx @@ -1,6 +1,6 @@ import React from "react"; -export default function LoadingCard({ message }: { message?: string }) { +export function LoadingCard({ message }: { message?: string }) { return (
diff --git a/explorer/src/components/Overlay.tsx b/explorer/src/components/common/Overlay.tsx similarity index 72% rename from explorer/src/components/Overlay.tsx rename to explorer/src/components/common/Overlay.tsx index ba8c792d2a..ad724d815c 100644 --- a/explorer/src/components/Overlay.tsx +++ b/explorer/src/components/common/Overlay.tsx @@ -4,6 +4,6 @@ type OverlayProps = { show: boolean; }; -export default function Overlay({ show }: OverlayProps) { +export function Overlay({ show }: OverlayProps) { return
; } diff --git a/explorer/src/components/common/Signature.tsx b/explorer/src/components/common/Signature.tsx index 0b27ddbcd3..a3961ca3e9 100644 --- a/explorer/src/components/common/Signature.tsx +++ b/explorer/src/components/common/Signature.tsx @@ -10,7 +10,7 @@ type Props = { link?: boolean; }; -export default function Signature({ signature, alignRight, link }: Props) { +export function Signature({ signature, alignRight, link }: Props) { const [state, setState] = useState("copy"); const copyToClipboard = () => navigator.clipboard.writeText(signature); diff --git a/explorer/src/components/common/TableCardBody.tsx b/explorer/src/components/common/TableCardBody.tsx index faef8fc19a..b47b945ce1 100644 --- a/explorer/src/components/common/TableCardBody.tsx +++ b/explorer/src/components/common/TableCardBody.tsx @@ -1,10 +1,6 @@ import React from "react"; -export default function TableCardBody({ - children, -}: { - children: React.ReactNode; -}) { +export function TableCardBody({ children }: { children: React.ReactNode }) { return (
diff --git a/explorer/src/components/instruction/RawDetails.tsx b/explorer/src/components/instruction/RawDetails.tsx index 1eb41e6931..6d19d6e5d2 100644 --- a/explorer/src/components/instruction/RawDetails.tsx +++ b/explorer/src/components/instruction/RawDetails.tsx @@ -1,8 +1,8 @@ import React from "react"; import bs58 from "bs58"; import { TransactionInstruction } from "@solana/web3.js"; -import Copyable from "components/Copyable"; -import Address from "components/common/Address"; +import { Copyable } from "components/common/Copyable"; +import { Address } from "components/common/Address"; function displayData(data: string) { if (data.length > 50) { diff --git a/explorer/src/components/instruction/RawParsedDetails.tsx b/explorer/src/components/instruction/RawParsedDetails.tsx index 1e856dc87f..8e930f7620 100644 --- a/explorer/src/components/instruction/RawParsedDetails.tsx +++ b/explorer/src/components/instruction/RawParsedDetails.tsx @@ -1,6 +1,6 @@ import React from "react"; import { ParsedInstruction } from "@solana/web3.js"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function RawParsedDetails({ ix }: { ix: ParsedInstruction }) { return ( diff --git a/explorer/src/components/instruction/stake/AuthorizeDetailsCard.tsx b/explorer/src/components/instruction/stake/AuthorizeDetailsCard.tsx index 00eccbff36..c76389d1e8 100644 --- a/explorer/src/components/instruction/stake/AuthorizeDetailsCard.tsx +++ b/explorer/src/components/instruction/stake/AuthorizeDetailsCard.tsx @@ -7,7 +7,7 @@ import { } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function AuthorizeDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/stake/DeactivateDetailsCard.tsx b/explorer/src/components/instruction/stake/DeactivateDetailsCard.tsx index ff40d71d81..8d9f96d426 100644 --- a/explorer/src/components/instruction/stake/DeactivateDetailsCard.tsx +++ b/explorer/src/components/instruction/stake/DeactivateDetailsCard.tsx @@ -7,7 +7,7 @@ import { } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function DeactivateDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/stake/DelegateDetailsCard.tsx b/explorer/src/components/instruction/stake/DelegateDetailsCard.tsx index 69d806d4b5..2102cdeece 100644 --- a/explorer/src/components/instruction/stake/DelegateDetailsCard.tsx +++ b/explorer/src/components/instruction/stake/DelegateDetailsCard.tsx @@ -7,7 +7,7 @@ import { } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function DelegateDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/stake/InitializeDetailsCard.tsx b/explorer/src/components/instruction/stake/InitializeDetailsCard.tsx index 153151b831..638bfffbb9 100644 --- a/explorer/src/components/instruction/stake/InitializeDetailsCard.tsx +++ b/explorer/src/components/instruction/stake/InitializeDetailsCard.tsx @@ -8,7 +8,7 @@ import { } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function InitializeDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/stake/SplitDetailsCard.tsx b/explorer/src/components/instruction/stake/SplitDetailsCard.tsx index e6191b0931..b728f5f341 100644 --- a/explorer/src/components/instruction/stake/SplitDetailsCard.tsx +++ b/explorer/src/components/instruction/stake/SplitDetailsCard.tsx @@ -8,7 +8,7 @@ import { import { lamportsToSolString } from "utils"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function SplitDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/stake/WithdrawDetailsCard.tsx b/explorer/src/components/instruction/stake/WithdrawDetailsCard.tsx index f403604e1c..d9a5cc3e2d 100644 --- a/explorer/src/components/instruction/stake/WithdrawDetailsCard.tsx +++ b/explorer/src/components/instruction/stake/WithdrawDetailsCard.tsx @@ -8,7 +8,7 @@ import { import { lamportsToSolString } from "utils"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function WithdrawDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/AllocateDetailsCard.tsx b/explorer/src/components/instruction/system/AllocateDetailsCard.tsx index 6b9c0220ca..81e06ae7bd 100644 --- a/explorer/src/components/instruction/system/AllocateDetailsCard.tsx +++ b/explorer/src/components/instruction/system/AllocateDetailsCard.tsx @@ -7,7 +7,7 @@ import { } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function AllocateDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/AllocateWithSeedDetailsCard.tsx b/explorer/src/components/instruction/system/AllocateWithSeedDetailsCard.tsx index 3b6f6e7881..a33f8f29f5 100644 --- a/explorer/src/components/instruction/system/AllocateWithSeedDetailsCard.tsx +++ b/explorer/src/components/instruction/system/AllocateWithSeedDetailsCard.tsx @@ -6,9 +6,9 @@ import { SystemInstruction, } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; -import Copyable from "components/Copyable"; +import { Copyable } from "components/common/Copyable"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function AllocateWithSeedDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/AssignDetailsCard.tsx b/explorer/src/components/instruction/system/AssignDetailsCard.tsx index ab40b83d2a..d45112828c 100644 --- a/explorer/src/components/instruction/system/AssignDetailsCard.tsx +++ b/explorer/src/components/instruction/system/AssignDetailsCard.tsx @@ -7,7 +7,7 @@ import { } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function AssignDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/AssignWithSeedDetailsCard.tsx b/explorer/src/components/instruction/system/AssignWithSeedDetailsCard.tsx index 9fc4f6764a..60cd247f8e 100644 --- a/explorer/src/components/instruction/system/AssignWithSeedDetailsCard.tsx +++ b/explorer/src/components/instruction/system/AssignWithSeedDetailsCard.tsx @@ -6,9 +6,9 @@ import { SystemInstruction, } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; -import Copyable from "components/Copyable"; +import { Copyable } from "components/common/Copyable"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function AssignWithSeedDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/CreateDetailsCard.tsx b/explorer/src/components/instruction/system/CreateDetailsCard.tsx index a140c86613..dcc0a8f93a 100644 --- a/explorer/src/components/instruction/system/CreateDetailsCard.tsx +++ b/explorer/src/components/instruction/system/CreateDetailsCard.tsx @@ -8,7 +8,7 @@ import { import { lamportsToSolString } from "utils"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function CreateDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/CreateWithSeedDetailsCard.tsx b/explorer/src/components/instruction/system/CreateWithSeedDetailsCard.tsx index e06348bb75..cce3ecd153 100644 --- a/explorer/src/components/instruction/system/CreateWithSeedDetailsCard.tsx +++ b/explorer/src/components/instruction/system/CreateWithSeedDetailsCard.tsx @@ -7,9 +7,9 @@ import { } from "@solana/web3.js"; import { lamportsToSolString } from "utils"; import { InstructionCard } from "../InstructionCard"; -import Copyable from "components/Copyable"; +import { Copyable } from "components/common/Copyable"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function CreateWithSeedDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/NonceAdvanceDetailsCard.tsx b/explorer/src/components/instruction/system/NonceAdvanceDetailsCard.tsx index b5478ed6be..5047bf6d9e 100644 --- a/explorer/src/components/instruction/system/NonceAdvanceDetailsCard.tsx +++ b/explorer/src/components/instruction/system/NonceAdvanceDetailsCard.tsx @@ -7,7 +7,7 @@ import { } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function NonceAdvanceDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/NonceAuthorizeDetailsCard.tsx b/explorer/src/components/instruction/system/NonceAuthorizeDetailsCard.tsx index 94a186b66d..3d0d794212 100644 --- a/explorer/src/components/instruction/system/NonceAuthorizeDetailsCard.tsx +++ b/explorer/src/components/instruction/system/NonceAuthorizeDetailsCard.tsx @@ -7,7 +7,7 @@ import { } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function NonceAuthorizeDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/NonceInitializeDetailsCard.tsx b/explorer/src/components/instruction/system/NonceInitializeDetailsCard.tsx index de3b6f8fb3..0e3c5208d5 100644 --- a/explorer/src/components/instruction/system/NonceInitializeDetailsCard.tsx +++ b/explorer/src/components/instruction/system/NonceInitializeDetailsCard.tsx @@ -7,7 +7,7 @@ import { } from "@solana/web3.js"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function NonceInitializeDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/NonceWithdrawDetailsCard.tsx b/explorer/src/components/instruction/system/NonceWithdrawDetailsCard.tsx index 20ad6a7989..2729fd699f 100644 --- a/explorer/src/components/instruction/system/NonceWithdrawDetailsCard.tsx +++ b/explorer/src/components/instruction/system/NonceWithdrawDetailsCard.tsx @@ -8,7 +8,7 @@ import { import { lamportsToSolString } from "utils"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function NonceWithdrawDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/system/TransferDetailsCard.tsx b/explorer/src/components/instruction/system/TransferDetailsCard.tsx index 6a1d58e496..02c7050a2e 100644 --- a/explorer/src/components/instruction/system/TransferDetailsCard.tsx +++ b/explorer/src/components/instruction/system/TransferDetailsCard.tsx @@ -8,7 +8,7 @@ import { import { lamportsToSolString } from "utils"; import { InstructionCard } from "../InstructionCard"; import { UnknownDetailsCard } from "../UnknownDetailsCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; export function TransferDetailsCard(props: { ix: TransactionInstruction; diff --git a/explorer/src/components/instruction/token/TokenDetailsCard.tsx b/explorer/src/components/instruction/token/TokenDetailsCard.tsx index c974e8fce7..385401c1c4 100644 --- a/explorer/src/components/instruction/token/TokenDetailsCard.tsx +++ b/explorer/src/components/instruction/token/TokenDetailsCard.tsx @@ -9,7 +9,7 @@ import { import { UnknownDetailsCard } from "../UnknownDetailsCard"; import { InstructionCard } from "../InstructionCard"; -import Address from "components/common/Address"; +import { Address } from "components/common/Address"; import { ParsedInstructionInfo, IX_STRUCTS } from "./types"; const IX_TITLES = { diff --git a/explorer/src/pages/AccountDetailsPage.tsx b/explorer/src/pages/AccountDetailsPage.tsx new file mode 100644 index 0000000000..3b14fbe348 --- /dev/null +++ b/explorer/src/pages/AccountDetailsPage.tsx @@ -0,0 +1,115 @@ +import React from "react"; +import { PublicKey, StakeProgram } from "@solana/web3.js"; +import { + FetchStatus, + useFetchAccountInfo, + useAccountInfo, +} from "providers/accounts"; +import { StakeAccountSection } from "components/account/StakeAccountSection"; +import { ErrorCard } from "components/common/ErrorCard"; +import { LoadingCard } from "components/common/LoadingCard"; +import { useCluster, ClusterStatus } from "providers/cluster"; +import { NavLink } from "react-router-dom"; +import { clusterPath } from "utils/url"; +import { UnknownAccountCard } from "components/account/UnknownAccountCard"; +import { OwnedTokensCard } from "components/account/OwnedTokensCard"; +import { TransactionHistoryCard } from "components/account/TransactionHistoryCard"; + +type Props = { address: string; tab?: string }; +export function AccountDetailsPage({ address, tab }: Props) { + let pubkey: PublicKey | undefined; + try { + pubkey = new PublicKey(address); + } catch (err) { + console.error(err); + // TODO handle bad addresses + } + + let moreTab: MoreTabs = "history"; + if (tab === "history" || tab === "tokens") { + moreTab = tab; + } + + return ( +
+
+
+
Details
+

Account

+
+
+ {pubkey && } + {pubkey && } +
+ ); +} + +function InfoSection({ pubkey }: { pubkey: PublicKey }) { + const fetchAccount = useFetchAccountInfo(); + const address = pubkey.toBase58(); + const info = useAccountInfo(address); + const refresh = useFetchAccountInfo(); + const { status } = useCluster(); + + // Fetch account on load + React.useEffect(() => { + if (!info && status === ClusterStatus.Connected) fetchAccount(pubkey); + }, [address, status]); // eslint-disable-line react-hooks/exhaustive-deps + + if (!info || info.status === FetchStatus.Fetching) { + return ; + } else if ( + info.status === FetchStatus.FetchFailed || + info.lamports === undefined + ) { + return refresh(pubkey)} text="Fetch Failed" />; + } + + const owner = info.details?.owner; + const data = info.details?.data; + if (data && owner && owner.equals(StakeProgram.programId)) { + return ; + } else { + return ; + } +} + +type MoreTabs = "history" | "tokens"; +function MoreSection({ pubkey, tab }: { pubkey: PublicKey; tab: MoreTabs }) { + const address = pubkey.toBase58(); + const info = useAccountInfo(address); + if (!info || info.lamports === undefined) return null; + + return ( + <> +
+
+
+
    +
  • + + History + +
  • +
  • + + Tokens + +
  • +
+
+
+
+ {tab === "tokens" && } + {tab === "history" && } + + ); +} diff --git a/explorer/src/components/StatsCard.tsx b/explorer/src/pages/ClusterStatsPage.tsx similarity index 92% rename from explorer/src/components/StatsCard.tsx rename to explorer/src/pages/ClusterStatsPage.tsx index d9c613cf74..b351e0497e 100644 --- a/explorer/src/components/StatsCard.tsx +++ b/explorer/src/pages/ClusterStatsPage.tsx @@ -1,7 +1,7 @@ import React from "react"; import CountUp from "react-countup"; -import TableCardBody from "./common/TableCardBody"; +import { TableCardBody } from "components/common/TableCardBody"; import { useDashboardInfo, usePerformanceInfo, @@ -12,17 +12,19 @@ import { import { slotsToHumanString } from "utils"; import { useCluster, Cluster } from "providers/cluster"; -export default function StatsCard() { +export function ClusterStatsPage() { return ( -
-
-
-
-

Live Cluster Stats

+
+
+
+
+
+

Live Cluster Stats

+
+
-
); } diff --git a/explorer/src/pages/SupplyPage.tsx b/explorer/src/pages/SupplyPage.tsx new file mode 100644 index 0000000000..892548e759 --- /dev/null +++ b/explorer/src/pages/SupplyPage.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import { TopAccountsCard } from "components/TopAccountsCard"; +import { SupplyCard } from "components/SupplyCard"; + +export function SupplyPage() { + return ( +
+ + +
+ ); +} diff --git a/explorer/src/components/TransactionDetails.tsx b/explorer/src/pages/TransactionDetailsPage.tsx similarity index 93% rename from explorer/src/components/TransactionDetails.tsx rename to explorer/src/pages/TransactionDetailsPage.tsx index 68cedf69a4..b024313ac6 100644 --- a/explorer/src/components/TransactionDetails.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -4,7 +4,7 @@ import { useTransactionStatus, useTransactionDetails, FetchStatus, -} from "../providers/transactions"; +} from "providers/transactions"; import { useFetchTransactionDetails } from "providers/transactions/details"; import { useCluster, ClusterStatus } from "providers/cluster"; import { @@ -14,22 +14,22 @@ import { SystemInstruction, } from "@solana/web3.js"; import { lamportsToSolString } from "utils"; -import { UnknownDetailsCard } from "./instruction/UnknownDetailsCard"; -import { SystemDetailsCard } from "./instruction/system/SystemDetailsCard"; -import { StakeDetailsCard } from "./instruction/stake/StakeDetailsCard"; -import ErrorCard from "./common/ErrorCard"; -import LoadingCard from "./common/LoadingCard"; -import TableCardBody from "./common/TableCardBody"; +import { UnknownDetailsCard } from "components/instruction/UnknownDetailsCard"; +import { SystemDetailsCard } from "components/instruction/system/SystemDetailsCard"; +import { StakeDetailsCard } from "components/instruction/stake/StakeDetailsCard"; +import { ErrorCard } from "components/common/ErrorCard"; +import { LoadingCard } from "components/common/LoadingCard"; +import { TableCardBody } from "components/common/TableCardBody"; import { displayTimestamp } from "utils/date"; -import InfoTooltip from "components/InfoTooltip"; +import { InfoTooltip } from "components/common/InfoTooltip"; import { isCached } from "providers/transactions/cached"; -import Address from "./common/Address"; -import Signature from "./common/Signature"; +import { Address } from "components/common/Address"; +import { Signature } from "components/common/Signature"; import { intoTransactionInstruction } from "utils/tx"; -import { TokenDetailsCard } from "./instruction/token/TokenDetailsCard"; +import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard"; type Props = { signature: TransactionSignature }; -export default function TransactionDetails({ signature }: Props) { +export function TransactionDetailsPage({ signature }: Props) { return (