react boundary

This commit is contained in:
tresabhi committed 2026-05-19 19:02:22 -05:00
1 parent 2b955a281b
commit 5ea7bbd6a1
7 files changed
+139 -108

No files matched your search

-8
View File
@@ -1,12 +1,4 @@
{
"error": {
"heading": "BlitzKit ran into a critical error",
"message": "Sorry about the inconvenience! You have a couple of options.",
"view": "View error",
"reload": "Reload",
"support": "Support"
},
"nations": {
"0": "Unspecified",
"1": "USSR",
@@ -1,72 +0,0 @@
---
import { api } from "../api/dynamic";
import { discordLink } from "../config/socials";
import { Button } from "./Button";
import { Heading } from "./Heading";
import { Link } from "./Link";
import { Text } from "./Text";
interface Props {
locale: string;
}
const { locale } = Astro.props;
const strings = await api.strings(locale);
---
<style>
.error-display {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-4);
padding: var(--space-4);
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
width: 100dvw;
height: 100dvh;
background-color: var(--gray-1);
body.error & {
display: flex;
}
.info {
display: flex;
align-items: center;
flex-direction: column;
gap: var(--space-3);
}
.options {
display: flex;
gap: var(--space-3);
}
}
</style>
<div class="error-display">
<div class="info">
<Heading size="2">{strings.error.heading}</Heading>
<Text lowContrast>{strings.error.message}</Text>
</div>
<div class="options">
<Button variant="outline">{strings.error.view}</Button>
<Link href="" underline="never">
<Button variant="outline">{strings.error.reload}</Button>
</Link>
<Link href={discordLink} target="_blank" underline="never">
<Button>{strings.error.support}</Button>
</Link>
</div>
</div>
@@ -1,20 +0,0 @@
import type { ReactNode } from "react";
import { ErrorBoundary } from "react-error-boundary";
interface Props {
children: ReactNode;
}
export function ErrorWrapper({ children }: Props) {
return (
<ErrorBoundary
fallback={null}
onError={() => {
console.log("what?");
document.body.classList.add("error");
}}
>
{children}
</ErrorBoundary>
);
}
@@ -0,0 +1,55 @@
.error-display {
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-4);
padding: var(--space-4);
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
width: 100dvw;
height: 100dvh;
text-align: center;
background-color: var(--gray-1);
.info {
display: flex;
align-items: center;
flex-direction: column;
gap: var(--space-3);
}
.error {
text-align: left;
overflow: hidden;
position: relative;
box-sizing: border-box;
max-width: min(100%, var(--page-width));
.cover {
position: absolute;
right: 0;
top: 0;
width: 25%;
height: 100%;
background: linear-gradient(90deg, transparent, var(--gray-1));
}
}
.options {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: var(--space-3);
}
}
@@ -0,0 +1,84 @@
import { useState, type ReactNode } from "react";
import {
ErrorBoundary,
getErrorMessage,
type FallbackProps,
} from "react-error-boundary";
import { discordLink } from "../../config/socials";
import { Button } from "../Button";
import { Code } from "../Code";
import { Heading } from "../Heading";
import { Link } from "../Link";
import { Text } from "../Text";
import styles from "./index.module.css";
interface Props {
children: ReactNode;
}
export function ErrorWrapper({ children }: Props) {
return (
<ErrorBoundary fallbackRender={(props) => <Fallback {...props} />}>
{children}
</ErrorBoundary>
);
}
function Fallback({ error, resetErrorBoundary }: FallbackProps) {
const [showError, setShowError] = useState(false);
let body: string | undefined;
if (error instanceof Error) {
body = error.stack || getErrorMessage(error);
} else {
body = getErrorMessage(error);
}
body ??= "No error message";
return (
<div className={styles["error-display"]}>
<div className={styles.info}>
<Heading size="3">BlitzKit crashed</Heading>
<Text lowContrast>Sorry about that! You have a couple of options.</Text>
</div>
{showError && (
<Code variant="solid" className={styles.error}>
{body}
<div className={styles.cover} />
</Code>
)}
<div className={styles.options}>
{!showError && (
<Button variant="outline" onClick={() => setShowError(true)}>
View error
</Button>
)}
{showError && (
<Button
variant="outline"
onClick={() => navigator.clipboard.writeText(body)}
>
Copy error
</Button>
)}
<Link href="" underline="never">
<Button variant="outline">Hard reload</Button>
</Link>
<Button variant="outline" onClick={resetErrorBoundary}>
Soft reset
</Button>
<Link href={discordLink} target="_blank" underline="never">
<Button>Discord server</Button>
</Link>
</div>
</div>
);
}
@@ -136,10 +136,6 @@ function EquipmentOption({
state.protagonist.equipment[equipmentIndex] === optionIndex,
);
if (typeof window !== "undefined") {
throw "test error";
}
return (
<Button
color={isSelected ? undefined : "gray"}
@@ -1,6 +1,4 @@
---
import ErrorDisplay from "../components/ErrorDisplay.astro";
import { api } from "../api/dynamic";
import BaseMeta from "../components/BaseMeta.astro";
import BaseStyles from "../components/BaseStyles.astro";
import ColorAlias from "../components/ColorAlias.astro";
@@ -79,8 +77,6 @@ const { accent, locale } = Astro.props;
<slot />
</div>
<ErrorDisplay {locale} />
<Footer {locale} />
</body>
</html>