Organize explorer file structure (#11464)
This commit is contained in:
parent
5a7e99f283
commit
c7eba80836
|
@ -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() {
|
|||
<SearchBar />
|
||||
<Switch>
|
||||
<Route exact path={["/supply", "/accounts", "accounts/top"]}>
|
||||
<div className="container mt-4">
|
||||
<SupplyCard />
|
||||
<TopAccountsCard />
|
||||
</div>
|
||||
<SupplyPage />
|
||||
</Route>
|
||||
<Route
|
||||
exact
|
||||
|
@ -37,7 +34,7 @@ function App() {
|
|||
(tx) => `/${tx}/:signature`
|
||||
)}
|
||||
render={({ match }) => (
|
||||
<TransactionDetails signature={match.params.signature} />
|
||||
<TransactionDetailsPage signature={match.params.signature} />
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
|
@ -58,16 +55,14 @@ function App() {
|
|||
exact
|
||||
path={["/address/:address", "/address/:address/:tab"]}
|
||||
render={({ match }) => (
|
||||
<AccountDetails
|
||||
<AccountDetailsPage
|
||||
address={match.params.address}
|
||||
tab={match.params.tab}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route exact path="/">
|
||||
<div className="container mt-4">
|
||||
<StatsCard />
|
||||
</div>
|
||||
<ClusterStatsPage />
|
||||
</Route>
|
||||
<Route
|
||||
render={({ location }) => (
|
||||
|
|
|
@ -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 (
|
||||
<div className="container mt-n3">
|
||||
<div className="header">
|
||||
<div className="header-body">
|
||||
<h6 className="header-pretitle">Details</h6>
|
||||
<h4 className="header-title">Account</h4>
|
||||
</div>
|
||||
</div>
|
||||
{pubkey && <AccountCards pubkey={pubkey} />}
|
||||
{pubkey && <MoreSection pubkey={pubkey} tab={moreTab} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div className="container">
|
||||
<div className="header">
|
||||
<div className="header-body pt-0">
|
||||
<ul className="nav nav-tabs nav-overflow header-tabs">
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
className="nav-link"
|
||||
to={clusterPath(`/address/${address}`)}
|
||||
exact
|
||||
>
|
||||
History
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
className="nav-link"
|
||||
to={clusterPath(`/address/${address}/tokens`)}
|
||||
exact
|
||||
>
|
||||
Tokens
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{tab === "tokens" && <TokensCard pubkey={pubkey} />}
|
||||
{tab === "history" && <HistoryCard pubkey={pubkey} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 <LoadingCard />;
|
||||
} else if (
|
||||
info.status === FetchStatus.FetchFailed ||
|
||||
info.lamports === undefined
|
||||
) {
|
||||
return <ErrorCard retry={() => refresh(pubkey)} text="Fetch Failed" />;
|
||||
}
|
||||
|
||||
const owner = info.details?.owner;
|
||||
const data = info.details?.data;
|
||||
if (data && owner && owner.equals(StakeProgram.programId)) {
|
||||
return <StakeAccountCards account={info} stakeAccount={data} />;
|
||||
} else {
|
||||
return <UnknownAccountCard account={info} />;
|
||||
}
|
||||
}
|
||||
|
||||
function UnknownAccountCard({ account }: { account: Account }) {
|
||||
const { details, lamports } = account;
|
||||
if (lamports === undefined) return null;
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header align-items-center">
|
||||
<h3 className="card-header-title">Overview</h3>
|
||||
</div>
|
||||
|
||||
<TableCardBody>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={account.pubkey} alignRight />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-right text-uppercase">
|
||||
{lamportsToSolString(lamports)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{details && (
|
||||
<tr>
|
||||
<td>Data (Bytes)</td>
|
||||
<td className="text-lg-right">{details.space}</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{details && (
|
||||
<tr>
|
||||
<td>Owner</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={details.owner} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{details && (
|
||||
<tr>
|
||||
<td>Executable</td>
|
||||
<td className="text-lg-right">
|
||||
{details.executable ? "Yes" : "No"}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 <LoadingCard message="Loading owned tokens" />;
|
||||
} else if (tokens === undefined) {
|
||||
return <ErrorCard retry={refresh} text="Failed to fetch owned tokens" />;
|
||||
}
|
||||
|
||||
if (tokens.length === 0) {
|
||||
return (
|
||||
<ErrorCard
|
||||
retry={refresh}
|
||||
retryText="Try Again"
|
||||
text={"No owned tokens found"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const mappedTokens = new Map<string, TokenAccountData>();
|
||||
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(
|
||||
<tr key={mintAddress}>
|
||||
<td>
|
||||
<Address pubkey={new PublicKey(mintAddress)} link />
|
||||
</td>
|
||||
<td>{balance}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header align-items-center">
|
||||
<h3 className="card-header-title">Owned Tokens</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
disabled={fetching}
|
||||
onClick={refresh}
|
||||
>
|
||||
{fetching ? (
|
||||
<>
|
||||
<span className="spinner-grow spinner-grow-sm mr-2"></span>
|
||||
Loading
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="fe fe-refresh-cw mr-2"></span>
|
||||
Refresh
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-muted">Token Address</th>
|
||||
<th className="text-muted">Balance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="list">{detailsList}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 <LoadingCard message="Loading history" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorCard retry={refresh} text="Failed to fetch transaction history" />
|
||||
);
|
||||
}
|
||||
|
||||
if (history.fetched.length === 0) {
|
||||
if (history.status === FetchStatus.Fetching) {
|
||||
return <LoadingCard message="Loading history" />;
|
||||
}
|
||||
return (
|
||||
<ErrorCard
|
||||
retry={loadMore}
|
||||
retryText="Try again"
|
||||
text="No transaction history found"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
<tr key={signature}>
|
||||
<td className="w-1">{slot}</td>
|
||||
|
||||
<td>
|
||||
<span className={`badge badge-soft-${statusClass}`}>
|
||||
{statusText}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<Signature signature={signature} link />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const fetching = history.status === FetchStatus.Fetching;
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header align-items-center">
|
||||
<h3 className="card-header-title">Transaction History</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
disabled={fetching}
|
||||
onClick={refresh}
|
||||
>
|
||||
{fetching ? (
|
||||
<>
|
||||
<span className="spinner-grow spinner-grow-sm mr-2"></span>
|
||||
Loading
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="fe fe-refresh-cw mr-2"></span>
|
||||
Refresh
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-muted w-1">Slot</th>
|
||||
<th className="text-muted">Result</th>
|
||||
<th className="text-muted">Transaction Signature</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="list">{detailsList}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="card-footer">
|
||||
{history.foundOldest ? (
|
||||
<div className="text-muted text-center">Fetched full history</div>
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-primary w-100"
|
||||
onClick={loadMore}
|
||||
disabled={fetching}
|
||||
>
|
||||
{fetching ? (
|
||||
<>
|
||||
<span className="spinner-grow spinner-grow-sm mr-2"></span>
|
||||
Loading
|
||||
</>
|
||||
) : (
|
||||
"Load More"
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ClusterModal;
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
ClusterStatus,
|
||||
Cluster,
|
||||
useClusterModal,
|
||||
} from "../providers/cluster";
|
||||
} from "providers/cluster";
|
||||
|
||||
export function ClusterStatusBanner() {
|
||||
const [, setShow] = useClusterModal();
|
||||
|
|
|
@ -25,7 +25,7 @@ const announcements = new Map<Cluster, Announcement>();
|
|||
// "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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 <LoadingCard message="Loading owned tokens" />;
|
||||
} else if (tokens === undefined) {
|
||||
return <ErrorCard retry={refresh} text="Failed to fetch owned tokens" />;
|
||||
}
|
||||
|
||||
if (tokens.length === 0) {
|
||||
return (
|
||||
<ErrorCard
|
||||
retry={refresh}
|
||||
retryText="Try Again"
|
||||
text={"No owned tokens found"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const mappedTokens = new Map<string, TokenAccountData>();
|
||||
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(
|
||||
<tr key={mintAddress}>
|
||||
<td>
|
||||
<Address pubkey={new PublicKey(mintAddress)} link />
|
||||
</td>
|
||||
<td>{balance}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header align-items-center">
|
||||
<h3 className="card-header-title">Owned Tokens</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
disabled={fetching}
|
||||
onClick={refresh}
|
||||
>
|
||||
{fetching ? (
|
||||
<>
|
||||
<span className="spinner-grow spinner-grow-sm mr-2"></span>
|
||||
Loading
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="fe fe-refresh-cw mr-2"></span>
|
||||
Refresh
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-muted">Token Address</th>
|
||||
<th className="text-muted">Balance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="list">{detailsList}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
}: {
|
|
@ -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 <LoadingCard message="Loading history" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorCard retry={refresh} text="Failed to fetch transaction history" />
|
||||
);
|
||||
}
|
||||
|
||||
if (history.fetched.length === 0) {
|
||||
if (history.status === FetchStatus.Fetching) {
|
||||
return <LoadingCard message="Loading history" />;
|
||||
}
|
||||
return (
|
||||
<ErrorCard
|
||||
retry={loadMore}
|
||||
retryText="Try again"
|
||||
text="No transaction history found"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
<tr key={signature}>
|
||||
<td className="w-1">{slot}</td>
|
||||
|
||||
<td>
|
||||
<span className={`badge badge-soft-${statusClass}`}>
|
||||
{statusText}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<Signature signature={signature} link />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const fetching = history.status === FetchStatus.Fetching;
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header align-items-center">
|
||||
<h3 className="card-header-title">Transaction History</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
disabled={fetching}
|
||||
onClick={refresh}
|
||||
>
|
||||
{fetching ? (
|
||||
<>
|
||||
<span className="spinner-grow spinner-grow-sm mr-2"></span>
|
||||
Loading
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="fe fe-refresh-cw mr-2"></span>
|
||||
Refresh
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-muted w-1">Slot</th>
|
||||
<th className="text-muted">Result</th>
|
||||
<th className="text-muted">Transaction Signature</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="list">{detailsList}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="card-footer">
|
||||
{history.foundOldest ? (
|
||||
<div className="text-muted text-center">Fetched full history</div>
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-primary w-100"
|
||||
onClick={loadMore}
|
||||
disabled={fetching}
|
||||
>
|
||||
{fetching ? (
|
||||
<>
|
||||
<span className="spinner-grow spinner-grow-sm mr-2"></span>
|
||||
Loading
|
||||
</>
|
||||
) : (
|
||||
"Load More"
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<div className="card">
|
||||
<div className="card-header align-items-center">
|
||||
<h3 className="card-header-title">Overview</h3>
|
||||
</div>
|
||||
|
||||
<TableCardBody>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={account.pubkey} alignRight />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-right text-uppercase">
|
||||
{lamportsToSolString(lamports)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{details && (
|
||||
<tr>
|
||||
<td>Data (Bytes)</td>
|
||||
<td className="text-lg-right">{details.space}</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{details && (
|
||||
<tr>
|
||||
<td>Owner</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={details.owner} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{details && (
|
||||
<tr>
|
||||
<td>Executable</td>
|
||||
<td className="text-lg-right">
|
||||
{details.executable ? "Yes" : "No"}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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<CopyState>("copy");
|
||||
const address = pubkey.toBase58();
|
||||
|
||||
|
|
|
@ -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<State>("hide");
|
||||
|
||||
const copyToClipboard = () => navigator.clipboard.writeText(text);
|
||||
|
@ -54,5 +54,3 @@ function Copyable({ bottom, right, text, children }: CopyableProps) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Copyable;
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
export default function ErrorCard({
|
||||
export function ErrorCard({
|
||||
retry,
|
||||
retryText,
|
||||
text,
|
||||
|
|
|
@ -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<State>("hide");
|
||||
|
||||
const justify = right ? "end" : "start";
|
||||
|
@ -51,5 +51,3 @@ function InfoTooltip({ bottom, right, text, children }: Props) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default InfoTooltip;
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
export default function LoadingCard({ message }: { message?: string }) {
|
||||
export function LoadingCard({ message }: { message?: string }) {
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-body text-center">
|
||||
|
|
|
@ -4,6 +4,6 @@ type OverlayProps = {
|
|||
show: boolean;
|
||||
};
|
||||
|
||||
export default function Overlay({ show }: OverlayProps) {
|
||||
export function Overlay({ show }: OverlayProps) {
|
||||
return <div className={`modal-backdrop fade${show ? " show" : ""}`}></div>;
|
||||
}
|
|
@ -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<CopyState>("copy");
|
||||
|
||||
const copyToClipboard = () => navigator.clipboard.writeText(signature);
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
export default function TableCardBody({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
export function TableCardBody({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 (
|
||||
<div className="container mt-n3">
|
||||
<div className="header">
|
||||
<div className="header-body">
|
||||
<h6 className="header-pretitle">Details</h6>
|
||||
<h4 className="header-title">Account</h4>
|
||||
</div>
|
||||
</div>
|
||||
{pubkey && <InfoSection pubkey={pubkey} />}
|
||||
{pubkey && <MoreSection pubkey={pubkey} tab={moreTab} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 <LoadingCard />;
|
||||
} else if (
|
||||
info.status === FetchStatus.FetchFailed ||
|
||||
info.lamports === undefined
|
||||
) {
|
||||
return <ErrorCard retry={() => refresh(pubkey)} text="Fetch Failed" />;
|
||||
}
|
||||
|
||||
const owner = info.details?.owner;
|
||||
const data = info.details?.data;
|
||||
if (data && owner && owner.equals(StakeProgram.programId)) {
|
||||
return <StakeAccountSection account={info} stakeAccount={data} />;
|
||||
} else {
|
||||
return <UnknownAccountCard account={info} />;
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div className="container">
|
||||
<div className="header">
|
||||
<div className="header-body pt-0">
|
||||
<ul className="nav nav-tabs nav-overflow header-tabs">
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
className="nav-link"
|
||||
to={clusterPath(`/address/${address}`)}
|
||||
exact
|
||||
>
|
||||
History
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
className="nav-link"
|
||||
to={clusterPath(`/address/${address}/tokens`)}
|
||||
exact
|
||||
>
|
||||
Tokens
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{tab === "tokens" && <OwnedTokensCard pubkey={pubkey} />}
|
||||
{tab === "history" && <TransactionHistoryCard pubkey={pubkey} />}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<h4 className="card-header-title">Live Cluster Stats</h4>
|
||||
<div className="container mt-4">
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<h4 className="card-header-title">Live Cluster Stats</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<StatsCardBody />
|
||||
</div>
|
||||
<StatsCardBody />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import React from "react";
|
||||
import { TopAccountsCard } from "components/TopAccountsCard";
|
||||
import { SupplyCard } from "components/SupplyCard";
|
||||
|
||||
export function SupplyPage() {
|
||||
return (
|
||||
<div className="container mt-4">
|
||||
<SupplyCard />
|
||||
<TopAccountsCard />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<div className="container mt-n3">
|
||||
<div className="header">
|
Loading…
Reference in New Issue