Explorer: fix slot links and block details page on devnet (#13274)
This commit is contained in:
		@@ -6,32 +6,31 @@ import { Signature } from "components/common/Signature";
 | 
				
			|||||||
import { ErrorCard } from "components/common/ErrorCard";
 | 
					import { ErrorCard } from "components/common/ErrorCard";
 | 
				
			||||||
import { LoadingCard } from "components/common/LoadingCard";
 | 
					import { LoadingCard } from "components/common/LoadingCard";
 | 
				
			||||||
import { Slot } from "components/common/Slot";
 | 
					import { Slot } from "components/common/Slot";
 | 
				
			||||||
 | 
					import { ClusterStatus, useCluster } from "providers/cluster";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function BlockHistoryCard({ slot }: { slot: number }) {
 | 
					export function BlockHistoryCard({ slot }: { slot: number }) {
 | 
				
			||||||
  const confirmedBlock = useBlock(slot);
 | 
					  const confirmedBlock = useBlock(slot);
 | 
				
			||||||
  const fetchBlock = useFetchBlock();
 | 
					  const fetchBlock = useFetchBlock();
 | 
				
			||||||
 | 
					  const { status } = useCluster();
 | 
				
			||||||
  const refresh = () => fetchBlock(slot);
 | 
					  const refresh = () => fetchBlock(slot);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Fetch block on load
 | 
				
			||||||
  React.useEffect(() => {
 | 
					  React.useEffect(() => {
 | 
				
			||||||
    if (!confirmedBlock) refresh();
 | 
					    if (!confirmedBlock && status === ClusterStatus.Connected) refresh();
 | 
				
			||||||
  }, [confirmedBlock, slot]); // eslint-disable-line react-hooks/exhaustive-deps
 | 
					  }, [slot, status]); // eslint-disable-line react-hooks/exhaustive-deps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!confirmedBlock) {
 | 
					  if (!confirmedBlock || confirmedBlock.status === FetchStatus.Fetching) {
 | 
				
			||||||
    return null;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (confirmedBlock.data === undefined) {
 | 
					 | 
				
			||||||
    if (confirmedBlock.status === FetchStatus.Fetching) {
 | 
					 | 
				
			||||||
    return <LoadingCard message="Loading block" />;
 | 
					    return <LoadingCard message="Loading block" />;
 | 
				
			||||||
    }
 | 
					  } else if (
 | 
				
			||||||
 | 
					    confirmedBlock.data === undefined ||
 | 
				
			||||||
    return <ErrorCard retry={refresh} text="Failed to fetch block" />;
 | 
					    confirmedBlock.status === FetchStatus.FetchFailed
 | 
				
			||||||
  }
 | 
					  ) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (confirmedBlock.status === FetchStatus.FetchFailed) {
 | 
					 | 
				
			||||||
    return <ErrorCard retry={refresh} text="Failed to fetch block" />;
 | 
					    return <ErrorCard retry={refresh} text="Failed to fetch block" />;
 | 
				
			||||||
 | 
					  } else if (confirmedBlock.data.block === undefined) {
 | 
				
			||||||
 | 
					    return <ErrorCard retry={refresh} text={`Block ${slot} was not found`} />;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const block = confirmedBlock.data.block;
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <div className="card">
 | 
					      <div className="card">
 | 
				
			||||||
@@ -44,31 +43,31 @@ export function BlockHistoryCard({ slot }: { slot: number }) {
 | 
				
			|||||||
          <tr>
 | 
					          <tr>
 | 
				
			||||||
            <td className="w-100">Slot</td>
 | 
					            <td className="w-100">Slot</td>
 | 
				
			||||||
            <td className="text-lg-right text-monospace">
 | 
					            <td className="text-lg-right text-monospace">
 | 
				
			||||||
              <Slot slot={Number(slot)} />
 | 
					              <Slot slot={slot} />
 | 
				
			||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
          <tr>
 | 
					          <tr>
 | 
				
			||||||
            <td className="w-100">Parent Slot</td>
 | 
					            <td className="w-100">Parent Slot</td>
 | 
				
			||||||
            <td className="text-lg-right text-monospace">
 | 
					            <td className="text-lg-right text-monospace">
 | 
				
			||||||
              <Slot slot={confirmedBlock.data.parentSlot} link />
 | 
					              <Slot slot={block.parentSlot} link />
 | 
				
			||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
          <tr>
 | 
					          <tr>
 | 
				
			||||||
            <td className="w-100">Blockhash</td>
 | 
					            <td className="w-100">Blockhash</td>
 | 
				
			||||||
            <td className="text-lg-right text-monospace">
 | 
					            <td className="text-lg-right text-monospace">
 | 
				
			||||||
              <span>{confirmedBlock.data.blockhash}</span>
 | 
					              <span>{block.blockhash}</span>
 | 
				
			||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
          <tr>
 | 
					          <tr>
 | 
				
			||||||
            <td className="w-100">Previous Blockhash</td>
 | 
					            <td className="w-100">Previous Blockhash</td>
 | 
				
			||||||
            <td className="text-lg-right text-monospace">
 | 
					            <td className="text-lg-right text-monospace">
 | 
				
			||||||
              <span>{confirmedBlock.data.previousBlockhash}</span>
 | 
					              <span>{block.previousBlockhash}</span>
 | 
				
			||||||
            </td>
 | 
					            </td>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
        </TableCardBody>
 | 
					        </TableCardBody>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {confirmedBlock.data.transactions.length === 0 ? (
 | 
					      {block.transactions.length === 0 ? (
 | 
				
			||||||
        <ErrorCard text="This block has no transactions" />
 | 
					        <ErrorCard text="This block has no transactions" />
 | 
				
			||||||
      ) : (
 | 
					      ) : (
 | 
				
			||||||
        <div className="card">
 | 
					        <div className="card">
 | 
				
			||||||
@@ -85,7 +84,7 @@ export function BlockHistoryCard({ slot }: { slot: number }) {
 | 
				
			|||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
              </thead>
 | 
					              </thead>
 | 
				
			||||||
              <tbody className="list">
 | 
					              <tbody className="list">
 | 
				
			||||||
                {confirmedBlock.data.transactions.map((tx, i) => {
 | 
					                {block.transactions.map((tx, i) => {
 | 
				
			||||||
                  let statusText;
 | 
					                  let statusText;
 | 
				
			||||||
                  let statusClass;
 | 
					                  let statusClass;
 | 
				
			||||||
                  let signature: React.ReactNode;
 | 
					                  let signature: React.ReactNode;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import React, { useState } from "react";
 | 
					import React, { useState } from "react";
 | 
				
			||||||
import { Link } from "react-router-dom";
 | 
					import { Link } from "react-router-dom";
 | 
				
			||||||
 | 
					import { clusterPath } from "utils/url";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CopyState = "copy" | "copied";
 | 
					type CopyState = "copy" | "copied";
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
@@ -30,7 +31,7 @@ export function Slot({ slot, link }: Props) {
 | 
				
			|||||||
  return link ? (
 | 
					  return link ? (
 | 
				
			||||||
    <span className="text-monospace">
 | 
					    <span className="text-monospace">
 | 
				
			||||||
      {copyButton}
 | 
					      {copyButton}
 | 
				
			||||||
      <Link className="" to={`/block/${slot}`}>
 | 
					      <Link to={clusterPath(`/block/${slot}`)}>
 | 
				
			||||||
        {slot.toLocaleString("en-US")}
 | 
					        {slot.toLocaleString("en-US")}
 | 
				
			||||||
      </Link>
 | 
					      </Link>
 | 
				
			||||||
    </span>
 | 
					    </span>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,8 +15,12 @@ export enum ActionType {
 | 
				
			|||||||
  Clear,
 | 
					  Clear,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type State = Cache.State<ConfirmedBlock>;
 | 
					type Block = {
 | 
				
			||||||
type Dispatch = Cache.Dispatch<ConfirmedBlock>;
 | 
					  block?: ConfirmedBlock;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type State = Cache.State<Block>;
 | 
				
			||||||
 | 
					type Dispatch = Cache.Dispatch<Block>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const StateContext = React.createContext<State | undefined>(undefined);
 | 
					const StateContext = React.createContext<State | undefined>(undefined);
 | 
				
			||||||
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
 | 
					const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
 | 
				
			||||||
@@ -25,7 +29,7 @@ type BlockProviderProps = { children: React.ReactNode };
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function BlockProvider({ children }: BlockProviderProps) {
 | 
					export function BlockProvider({ children }: BlockProviderProps) {
 | 
				
			||||||
  const { url } = useCluster();
 | 
					  const { url } = useCluster();
 | 
				
			||||||
  const [state, dispatch] = Cache.useReducer<ConfirmedBlock>(url);
 | 
					  const [state, dispatch] = Cache.useReducer<Block>(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  React.useEffect(() => {
 | 
					  React.useEffect(() => {
 | 
				
			||||||
    dispatch({ type: ActionType.Clear, url });
 | 
					    dispatch({ type: ActionType.Clear, url });
 | 
				
			||||||
@@ -40,9 +44,7 @@ export function BlockProvider({ children }: BlockProviderProps) {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useBlock(
 | 
					export function useBlock(key: number): Cache.CacheEntry<Block> | undefined {
 | 
				
			||||||
  key: number
 | 
					 | 
				
			||||||
): Cache.CacheEntry<ConfirmedBlock> | undefined {
 | 
					 | 
				
			||||||
  const context = React.useContext(StateContext);
 | 
					  const context = React.useContext(StateContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!context) {
 | 
					  if (!context) {
 | 
				
			||||||
@@ -66,18 +68,23 @@ export async function fetchBlock(
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let status: FetchStatus;
 | 
					  let status: FetchStatus;
 | 
				
			||||||
  let data: ConfirmedBlock | undefined;
 | 
					  let data: Block | undefined = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const connection = new Connection(url, "max");
 | 
					    const connection = new Connection(url, "max");
 | 
				
			||||||
    data = await connection.getConfirmedBlock(Number(key));
 | 
					    data = { block: await connection.getConfirmedBlock(Number(key)) };
 | 
				
			||||||
    status = FetchStatus.Fetched;
 | 
					    status = FetchStatus.Fetched;
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (err) {
 | 
				
			||||||
    console.log(error);
 | 
					    const error = err as Error;
 | 
				
			||||||
 | 
					    if (error.message.includes("not found")) {
 | 
				
			||||||
 | 
					      data = {} as Block;
 | 
				
			||||||
 | 
					      status = FetchStatus.Fetched;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      status = FetchStatus.FetchFailed;
 | 
				
			||||||
      if (cluster !== Cluster.Custom) {
 | 
					      if (cluster !== Cluster.Custom) {
 | 
				
			||||||
        Sentry.captureException(error, { tags: { url } });
 | 
					        Sentry.captureException(error, { tags: { url } });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    status = FetchStatus.FetchFailed;
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  dispatch({
 | 
					  dispatch({
 | 
				
			||||||
@@ -90,14 +97,12 @@ export async function fetchBlock(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useFetchBlock() {
 | 
					export function useFetchBlock() {
 | 
				
			||||||
  const { cluster, url } = useCluster();
 | 
					 | 
				
			||||||
  const state = React.useContext(StateContext);
 | 
					 | 
				
			||||||
  const dispatch = React.useContext(DispatchContext);
 | 
					  const dispatch = React.useContext(DispatchContext);
 | 
				
			||||||
 | 
					  if (!dispatch) {
 | 
				
			||||||
  if (!state || !dispatch) {
 | 
					 | 
				
			||||||
    throw new Error(`useFetchBlock must be used within a BlockProvider`);
 | 
					    throw new Error(`useFetchBlock must be used within a BlockProvider`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { cluster, url } = useCluster();
 | 
				
			||||||
  return React.useCallback(
 | 
					  return React.useCallback(
 | 
				
			||||||
    (key: number) => fetchBlock(dispatch, url, cluster, key),
 | 
					    (key: number) => fetchBlock(dispatch, url, cluster, key),
 | 
				
			||||||
    [dispatch, cluster, url]
 | 
					    [dispatch, cluster, url]
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user