From 9a43e87db302ec599fd1e97d8b77e2e68831017f Mon Sep 17 00:00:00 2001 From: Karolis Ramanauskas Date: Thu, 17 Oct 2024 21:43:25 +0300 Subject: [PATCH] fix(explorer): various fixes (#3299) --- .changeset/soft-bears-rest.md | 11 +++ .../src/app/(explorer)/[chainName]/layout.tsx | 19 ++++++ .../src/app/(explorer)/[chainName]/page.tsx | 4 +- .../[chainName]/worlds/WorldsForm.tsx | 2 +- .../worlds/[worldAddress]/interact/Form.tsx | 68 +++++++++---------- .../worlds/[worldAddress]/layout.tsx | 23 ++++++- .../observe/TransactionTableRow.tsx | 10 +-- .../worlds/[worldAddress]/page.tsx | 6 +- .../(explorer)/[chainName]/worlds/page.tsx | 15 ++-- .../app/(explorer)/queries/useTablesQuery.ts | 1 + .../src/app/(explorer)/utils/timeAgo.ts | 4 -- packages/explorer/src/common.ts | 12 +++- .../explorer/src/components/Navigation.tsx | 2 +- 13 files changed, 111 insertions(+), 66 deletions(-) create mode 100644 .changeset/soft-bears-rest.md create mode 100644 packages/explorer/src/app/(explorer)/[chainName]/layout.tsx diff --git a/.changeset/soft-bears-rest.md b/.changeset/soft-bears-rest.md new file mode 100644 index 0000000000..766f6fbc91 --- /dev/null +++ b/.changeset/soft-bears-rest.md @@ -0,0 +1,11 @@ +--- +"@latticexyz/explorer": patch +--- + +- Not found page if invalid chain name. +- Only show selector for worlds if options exist. +- Remove "future time" from transactions table. +- Improved layout for Interact tab. +- Wrap long args in transactions table. +- New tables polling. +- Add logs (regression). diff --git a/packages/explorer/src/app/(explorer)/[chainName]/layout.tsx b/packages/explorer/src/app/(explorer)/[chainName]/layout.tsx new file mode 100644 index 0000000000..816c9affda --- /dev/null +++ b/packages/explorer/src/app/(explorer)/[chainName]/layout.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { notFound } from "next/navigation"; +import { isValidChainName } from "../../../common"; + +type Props = { + params: { + chainName: string; + }; + children: React.ReactNode; +}; + +export default function ChainLayout({ params: { chainName }, children }: Props) { + if (!isValidChainName(chainName)) { + return notFound(); + } + + return children; +} diff --git a/packages/explorer/src/app/(explorer)/[chainName]/page.tsx b/packages/explorer/src/app/(explorer)/[chainName]/page.tsx index e6c8fd70b9..4e8538b59d 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/page.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/page.tsx @@ -6,6 +6,6 @@ type Props = { }; }; -export default async function ChainPage({ params }: Props) { - return redirect(`/${params.chainName}/worlds`); +export default async function ChainPage({ params: { chainName } }: Props) { + return redirect(`/${chainName}/worlds`); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx index a17b413515..7fe546dd6a 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/WorldsForm.tsx @@ -84,7 +84,7 @@ export function WorldsForm({ worlds }: { worlds: Address[] }) {
- {open ? ( + {open && worlds.length > 0 ? (
{worlds?.map((world) => { diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx index e6eb68ff84..dd9ba09c07 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/interact/Form.tsx @@ -22,26 +22,22 @@ export function Form() { ); return ( -
-
-
-

Jump to:

+ <> +
+
+
+

Jump to:

+ { + setFilterValue(evt.target.value); + }} + /> +
- { - setFilterValue(evt.target.value); - }} - /> - -
    +
      {!isFetched && Array.from({ length: 10 }).map((_, index) => { return ( @@ -77,25 +73,25 @@ export function Form() { })}
-
-
- {!isFetched && ( - <> - - - - - - - - - )} +
+ {!isFetched && ( + <> + + + + + + + + + )} - {filteredFunctions?.map((abi) => { - return ; - })} + {filteredFunctions?.map((abi) => { + return ; + })} +
-
+ ); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx index 2c8b5a4811..ffa329b67d 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/layout.tsx @@ -1,15 +1,32 @@ "use client"; +import { notFound } from "next/navigation"; +import { Address } from "viem"; +import { isValidChainName } from "../../../../../common"; import { Navigation } from "../../../../../components/Navigation"; import { Providers } from "./Providers"; import { TransactionsWatcher } from "./observe/TransactionsWatcher"; -export default function WorldLayout({ children }: { children: React.ReactNode }) { +type Props = { + params: { + chainName: string; + worldAddress: Address; + }; + children: React.ReactNode; +}; + +export default function WorldLayout({ params: { chainName }, children }: Props) { + if (!isValidChainName(chainName)) { + return notFound(); + } + return ( - +
+ + {children} +
- {children}
); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx index d828a5436f..d34d1f640d 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/observe/TransactionTableRow.tsx @@ -82,11 +82,11 @@ export function TransactionTableRow({ row }: { row: Row })

Inputs

{Array.isArray(data.functionData?.args) && data.functionData?.args.length > 0 ? ( -
+
{data.functionData?.args?.map((arg, idx) => (
arg {idx + 1}: - + {typeof arg === "object" && arg !== null ? JSON.stringify(arg, null, 2) : String(arg)}
@@ -113,8 +113,8 @@ export function TransactionTableRow({ row }: { row: Row }) <>
-

Logs

- {Array.isArray(logs) && logs.length > 10 ? ( +

Logs

+ {Array.isArray(logs) && logs.length > 0 ? (
    {logs.map((log, idx) => { @@ -128,7 +128,7 @@ export function TransactionTableRow({ row }: { row: Row }) {Object.entries(args).map(([key, value]) => (
  • {key}: - {value as never} + {value as never}
  • ))}
diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/page.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/page.tsx index 47b435ef54..a3b0aefcec 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/page.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/[worldAddress]/page.tsx @@ -1,13 +1,13 @@ import { redirect } from "next/navigation"; +import { supportedChainName } from "../../../../../common"; type Props = { params: { - chainName: string; + chainName: supportedChainName; worldAddress: string; }; }; -export default async function WorldPage({ params }: Props) { - const { chainName, worldAddress } = params; +export default async function WorldPage({ params: { chainName, worldAddress } }: Props) { return redirect(`/${chainName}/worlds/${worldAddress}/explore`); } diff --git a/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx b/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx index a8ab09a6aa..04fcbedb5c 100644 --- a/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx +++ b/packages/explorer/src/app/(explorer)/[chainName]/worlds/page.tsx @@ -1,7 +1,7 @@ import { headers } from "next/headers"; import { redirect } from "next/navigation"; import { Address } from "viem"; -import { supportedChains, validateChainName } from "../../../../common"; +import { supportedChainName, supportedChains } from "../../../../common"; import { indexerForChainId } from "../../utils/indexerForChainId"; import { WorldsForm } from "./WorldsForm"; @@ -13,9 +13,7 @@ type ApiResponse = { }[]; }; -async function fetchWorlds(chainName: string): Promise { - validateChainName(chainName); - +async function fetchWorlds(chainName: supportedChainName): Promise { const chain = supportedChains[chainName]; const indexer = indexerForChainId(chain.id); let worldsApiUrl: string | null = null; @@ -49,15 +47,14 @@ async function fetchWorlds(chainName: string): Promise { type Props = { params: { - chainName: string; + chainName: supportedChainName; }; }; -export default async function WorldsPage({ params }: Props) { - const worlds = await fetchWorlds(params.chainName); +export default async function WorldsPage({ params: { chainName } }: Props) { + const worlds = await fetchWorlds(chainName); if (worlds.length === 1) { - return redirect(`/${params.chainName}/worlds/${worlds[0]}`); + return redirect(`/${chainName}/worlds/${worlds[0]}`); } - return ; } diff --git a/packages/explorer/src/app/(explorer)/queries/useTablesQuery.ts b/packages/explorer/src/app/(explorer)/queries/useTablesQuery.ts index 197826c86c..8220d89803 100644 --- a/packages/explorer/src/app/(explorer)/queries/useTablesQuery.ts +++ b/packages/explorer/src/app/(explorer)/queries/useTablesQuery.ts @@ -52,5 +52,6 @@ export function useTablesQuery() { }) .sort(({ namespace }) => (internalNamespaces.includes(namespace) ? 1 : -1)); }, + refetchInterval: 5000, }); } diff --git a/packages/explorer/src/app/(explorer)/utils/timeAgo.ts b/packages/explorer/src/app/(explorer)/utils/timeAgo.ts index dc9f2bcac4..cd885031d6 100644 --- a/packages/explorer/src/app/(explorer)/utils/timeAgo.ts +++ b/packages/explorer/src/app/(explorer)/utils/timeAgo.ts @@ -11,10 +11,6 @@ export function timeAgo(timestamp: bigint) { const currentTimestampSeconds = Math.floor(Date.now() / 1000); const diff = currentTimestampSeconds - Number(timestamp); - if (diff < 0) { - return "in the future"; - } - for (const unit of units) { if (diff >= unit.limit) { const unitsAgo = Math.floor(diff / unit.inSeconds); diff --git a/packages/explorer/src/common.ts b/packages/explorer/src/common.ts index 06eebb04eb..da18b9bc32 100644 --- a/packages/explorer/src/common.ts +++ b/packages/explorer/src/common.ts @@ -13,14 +13,22 @@ export const chainIdToName = Object.fromEntries( Object.entries(supportedChains).map(([chainName, chain]) => [chain.id, chainName]), ) as Record; +export function isValidChainId(chainId: unknown): chainId is supportedChainId { + return typeof chainId === "number" && chainId in chainIdToName; +} + +export function isValidChainName(name: unknown): name is supportedChainName { + return typeof name === "string" && name in supportedChains; +} + export function validateChainId(chainId: unknown): asserts chainId is supportedChainId { - if (!(typeof chainId === "number" && chainId in chainIdToName)) { + if (!isValidChainId(chainId)) { throw new Error(`Invalid chain ID. Supported chains are: ${Object.keys(chainIdToName).join(", ")}.`); } } export function validateChainName(name: unknown): asserts name is supportedChainName { - if (!(typeof name === "string" && name in supportedChains)) { + if (!isValidChainName(name)) { throw new Error(`Invalid chain name. Supported chains are: ${Object.keys(supportedChains).join(", ")}.`); } } diff --git a/packages/explorer/src/components/Navigation.tsx b/packages/explorer/src/components/Navigation.tsx index 388ddad946..95921087fe 100644 --- a/packages/explorer/src/components/Navigation.tsx +++ b/packages/explorer/src/components/Navigation.tsx @@ -30,7 +30,7 @@ function NavigationLink({ href, children }: { href: string; children: React.Reac export function Navigation() { const { data, isFetched } = useWorldAbiQuery(); return ( -
+
Explore