Skip to content

Commit 88af932

Browse files
authored
refactor(entrykit): improve error handling (#3574)
1 parent 88949aa commit 88af932

29 files changed

Lines changed: 347 additions & 187 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@latticexyz/entrykit": patch
3+
---
4+
5+
Improved error handling.

packages/entrykit/mprocs.deploy.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ procs:
44
shell: anvil --dump-state playground/anvil-state.json
55

66
deploy-prereqs:
7-
shell: ./bin/deploy.js
7+
shell: pnpm run deploy
88
env:
99
DEBUG: "mud:*"
1010
# Anvil default account (0x70997970C51812dc3A010C7d01b50e0d17dc79C8)
@@ -18,7 +18,7 @@ procs:
1818

1919
deploy-game:
2020
cwd: ../../test/mock-game-contracts
21-
shell: pnpm deploy:local --salt 0x
21+
shell: pnpm run deploy:local --salt 0x
2222
env:
2323
DEBUG: "mud:*"
2424
# Anvil default account (0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc)

packages/entrykit/playground/anvil-state.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/entrykit/src/AccountModal.tsx

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { Modal } from "./ui/Modal";
22
import { useAccountModal } from "./useAccountModal";
33
import { twMerge } from "tailwind-merge";
44
import { AccountModalContent } from "./AccountModalContent";
5-
import { AccountModalErrorBoundary } from "./AccountModalErrorBoundary";
65
import { DialogClose, DialogTitle } from "@radix-ui/react-dialog";
76
import { CloseIcon } from "./icons/CloseIcon";
87
import { Logo } from "./icons/Logo";
8+
import { ErrorBoundary } from "react-error-boundary";
9+
import { ErrorFallback } from "./errors/ErrorFallback";
10+
import { ErrorsOverlay } from "./errors/ErrorsOverlay";
911

1012
export function AccountModal() {
1113
const { accountModalOpen, toggleAccountModal } = useAccountModal();
@@ -17,15 +19,27 @@ export function AccountModal() {
1719
<div
1820
className={twMerge(
1921
"relative py-2 ring-1",
20-
"bg-neutral-900 text-neutral-400 ring-neutral-700/50 divide-neutral-700",
22+
"bg-neutral-900 text-neutral-400 ring-neutral-700/50",
2123
"links:font-medium links:underline links:underline-offset-4",
2224
"links:text-white",
2325
"links:decoration-neutral-500 hover:links:decoration-orange-500",
2426
)}
2527
>
26-
<AccountModalErrorBoundary>
28+
<div className="absolute top-0 right-0">
29+
<DialogClose
30+
className={twMerge(
31+
"pointer-events-auto leading-none p-2 transition",
32+
"text-white/20 hover:text-white/40",
33+
)}
34+
title="Close"
35+
>
36+
<CloseIcon className="m-0" />
37+
</DialogClose>
38+
</div>
39+
<ErrorBoundary FallbackComponent={ErrorFallback}>
2740
<AccountModalContent />
28-
</AccountModalErrorBoundary>
41+
<ErrorsOverlay />
42+
</ErrorBoundary>
2943

3044
<a
3145
href="https://mud.dev"
@@ -38,18 +52,6 @@ export function AccountModal() {
3852
</span>
3953
<span>Powered by MUD</span>
4054
</a>
41-
42-
<div className="absolute top-0 right-0">
43-
<DialogClose
44-
className={twMerge(
45-
"pointer-events-auto leading-none p-2 transition",
46-
"text-neutral-700 hover:text-neutral-500",
47-
)}
48-
title="Close"
49-
>
50-
<CloseIcon className="m-0" />
51-
</DialogClose>
52-
</div>
5355
</div>
5456
) : null}
5557
</Modal>

packages/entrykit/src/AccountModalErrorBoundary.tsx

Lines changed: 0 additions & 52 deletions
This file was deleted.

packages/entrykit/src/AccountName.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,15 @@ import { Logo } from "./icons/Logo";
44
import { TruncatedHex } from "./ui/TruncatedHex";
55
import { usePreloadImage } from "./usePreloadImage";
66
import { twMerge } from "tailwind-merge";
7-
import { useEffect } from "react";
87

98
export type Props = {
109
address: Hex;
1110
};
1211

1312
export function AccountName({ address }: Props) {
14-
const { data: ens, error: ensError } = useENS(address);
13+
const { data: ens } = useENS(address);
1514
const avatar = usePreloadImage(ens?.avatar);
1615

17-
useEffect(() => {
18-
if (ensError) {
19-
console.log("Could not get ENS", ensError);
20-
}
21-
}, [ensError]);
22-
2316
return (
2417
<>
2518
<span className="flex-shrink-0 w-6 h-6 -my-1 -mx-0.5 grid place-items-center">

packages/entrykit/src/ErrorNotice.tsx

Lines changed: 0 additions & 37 deletions
This file was deleted.

packages/entrykit/src/bin/deploy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ const client = createClient({ account, transport: http(rpcUrl) });
4545

4646
const chainId = await getChainId(client);
4747

48+
console.log("Deploying to chain", chainId, "from", account.address, "via", rpcUrl);
49+
4850
// TODO: deployer address flag/env var?
4951
const deployerAddress = await ensureDeployer(client);
5052

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ReactNode } from "react";
2+
import { FallbackProps } from "react-error-boundary";
3+
import { ErrorOverlay } from "./ErrorOverlay";
4+
5+
export type Props = {
6+
children: ReactNode;
7+
};
8+
9+
export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
10+
return (
11+
<div className="h-64">
12+
<ErrorOverlay error={error} retry={resetErrorBoundary} />
13+
</div>
14+
);
15+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { wait } from "@latticexyz/common/utils";
2+
import { useEffect } from "react";
3+
import { twMerge } from "tailwind-merge";
4+
5+
export type Props = {
6+
error?: Error;
7+
dismiss?: () => unknown;
8+
retry?: () => unknown | Promise<unknown>;
9+
};
10+
11+
export function ErrorOverlay({ error, retry, dismiss }: Props) {
12+
useEffect(() => {
13+
if (error) {
14+
console.error(error);
15+
}
16+
}, [error]);
17+
18+
return (
19+
<div className="pointer-events-none absolute inset-0 overflow-clip">
20+
<div
21+
className={twMerge(
22+
"absolute inset-0 bg-blue-700/60",
23+
"transition duration-300",
24+
error ? "opacity-100 pointer-events-auto" : "opacity-0",
25+
)}
26+
/>
27+
<div
28+
className={twMerge(
29+
"absolute inset-0 pb-8",
30+
"transition duration-300",
31+
error ? "translate-y-0 opacity-100 pointer-events-auto" : "-translate-y-4 opacity-0",
32+
)}
33+
>
34+
{error ? (
35+
<>
36+
<div className="w-full max-h-full bg-blue-700 text-white/80 overflow-auto">
37+
<div className="space-y-6 px-8 pt-8">
38+
<div className="text-white text-lg font-bold">Oops! It broke :(</div>
39+
<div className="font-mono text-xs whitespace-pre-wrap">{error.message.trim()}</div>
40+
<div className="text-sm">See the console for more info.</div>
41+
</div>
42+
<div className="pointer-events-none sticky bottom-0 left-0 -mt-2">
43+
<div className="w-full h-12 bg-gradient-to-b from-transparent to-blue-700" />
44+
{retry ? (
45+
<div className="bg-blue-700 text-center">
46+
{/* TODO: replace with AsyncButton */}
47+
<button
48+
type="button"
49+
className={twMerge(
50+
"pointer-events-auto group w-24 p-1 -translate-y-2 transition",
51+
"bg-blue-600 hover:bg-blue-500 aria-busy:bg-blue-500",
52+
"text-white text-sm font-medium",
53+
"aria-busy:pointer-events-none",
54+
)}
55+
onClick={async (event) => {
56+
// if we retry and the same error occurs, it'll look like the button click did nothing
57+
// so we'll fake a pending state here to give users an indication something is happening
58+
event.currentTarget.ariaBusy = "true";
59+
await wait(500);
60+
retry();
61+
if (event.currentTarget) {
62+
event.currentTarget.ariaBusy = null;
63+
}
64+
}}
65+
>
66+
{/* TODO: swap with pending icon */}
67+
<span className="group-aria-busy:hidden">Retry</span>
68+
<span className="hidden group-aria-busy:inline">Retrying…</span>
69+
</button>
70+
</div>
71+
) : dismiss ? (
72+
<div className="bg-blue-700 text-center">
73+
{/* TODO: replace with AsyncButton */}
74+
<button
75+
type="button"
76+
className={twMerge(
77+
"pointer-events-auto group w-24 p-1 -translate-y-2 transition",
78+
"bg-blue-600 hover:bg-blue-500 aria-busy:bg-blue-500",
79+
"text-white text-sm font-medium",
80+
"aria-busy:pointer-events-none",
81+
)}
82+
onClick={dismiss}
83+
>
84+
Dismiss
85+
</button>
86+
</div>
87+
) : null}
88+
</div>
89+
</div>
90+
</>
91+
) : null}
92+
</div>
93+
</div>
94+
);
95+
}

0 commit comments

Comments
 (0)