Merge pull request #458 from danditomaso/feat/add-error-boundary
feat: add error boundary
This commit is contained in:
@@ -4,7 +4,6 @@ import {
|
||||
} from "@components/Form/DynamicFormField.tsx";
|
||||
import { FieldWrapper } from "@components/Form/FormWrapper.tsx";
|
||||
import { Button } from "@components/UI/Button.tsx";
|
||||
import { H4 } from "@components/UI/Typography/H4.tsx";
|
||||
import { Subtle } from "@components/UI/Typography/Subtle.tsx";
|
||||
import {
|
||||
type Control,
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
type SubmitHandler,
|
||||
useForm,
|
||||
} from "react-hook-form";
|
||||
import { Heading } from "../UI/Typography/Heading";
|
||||
|
||||
interface DisabledBy<T> {
|
||||
fieldName: Path<T>;
|
||||
@@ -96,7 +96,9 @@ export function DynamicForm<T extends FieldValues>({
|
||||
{fieldGroups.map((fieldGroup) => (
|
||||
<div key={fieldGroup.label} className="space-y-8 sm:space-y-5">
|
||||
<div>
|
||||
<H4 className="font-medium">{fieldGroup.label}</H4>
|
||||
<Heading as="h4" className="font-medium">
|
||||
{fieldGroup.label}
|
||||
</Heading>
|
||||
<Subtle>{fieldGroup.description}</Subtle>
|
||||
<Subtle className="font-semibold">{fieldGroup?.notes}</Subtle>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Separator } from "@app/components/UI/Seperator";
|
||||
import { H5 } from "@app/components/UI/Typography/H5.tsx";
|
||||
import { Heading } from "@app/components/UI/Typography/Heading";
|
||||
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx";
|
||||
import { formatQuantity } from "@app/core/utils/string";
|
||||
import { Avatar } from "@components/UI/Avatar";
|
||||
@@ -62,7 +62,7 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<H5>{name}</H5>
|
||||
<Heading as="h5">{name}</Heading>
|
||||
|
||||
{hardwareType !== "UNSET" && <Subtle>{hardwareType}</Subtle>}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@app/components/UI/Tooltip";
|
||||
import { useAppStore } from "@app/core/stores/appStore";
|
||||
import {
|
||||
type MessageWithState,
|
||||
useDeviceStore,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { cn } from "@app/core/utils/cn.ts";
|
||||
import { AlignLeftIcon, type LucideIcon } from "lucide-react";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import { ErrorPage } from "./UI/ErrorPage";
|
||||
import Footer from "./UI/Footer";
|
||||
import { Spinner } from "./UI/Spinner";
|
||||
|
||||
@@ -23,7 +25,7 @@ export const PageLayout = ({
|
||||
children,
|
||||
}: PageLayoutProps) => {
|
||||
return (
|
||||
<>
|
||||
<ErrorBoundary FallbackComponent={ErrorPage}>
|
||||
<div className="relative flex h-full w-full flex-col">
|
||||
<div className="flex h-14 shrink-0 border-b-[0.5px] border-slate-300 dark:border-slate-700 md:h-16 md:px-4">
|
||||
<button
|
||||
@@ -68,6 +70,6 @@ export const PageLayout = ({
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import newGithubIssueUrl from "@app/core/utils/github";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { Heading } from "./Typography/Heading";
|
||||
import { Link } from "./Typography/Link";
|
||||
import { P } from "./Typography/P";
|
||||
|
||||
export function ErrorPage({ error }: { error: Error }) {
|
||||
if (!error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<article className="w-full overflow-y-auto">
|
||||
<section className="flex shrink md:flex-row gap-16 mt-20 px-4 md:px-8 text-lg md:text-xl space-y-2 place-items-center">
|
||||
<div>
|
||||
<Heading as="h2" className="text-text-primary">
|
||||
This is a little embarrassing...
|
||||
</Heading>
|
||||
<P>
|
||||
We are really sorry but an error occurred in the web client that
|
||||
caused it to crash. <br />
|
||||
This is not supposed to happen, and we are working hard to fix it.
|
||||
</P>
|
||||
<P>
|
||||
The best way to prevent this from happening again to you or anyone
|
||||
else is to report the issue to us.
|
||||
</P>
|
||||
<P>Please include the following information in your report:</P>
|
||||
<ul className="list-disc list-inside text-sm">
|
||||
<li>What you were doing when the error occurred</li>
|
||||
<li>What you expected to happen</li>
|
||||
<li>What actually happened</li>
|
||||
<li>Any other relevant information</li>
|
||||
</ul>
|
||||
<P>
|
||||
You can report the issue to our{" "}
|
||||
<Link
|
||||
href={newGithubIssueUrl({
|
||||
repoUrl: "https://github.com/meshtastic/web",
|
||||
template: "bug.yml",
|
||||
title: "[Bug]: An unhandled error occurred. <Add details here>",
|
||||
logs: error?.stack,
|
||||
})}
|
||||
>
|
||||
Github
|
||||
</Link>
|
||||
<ExternalLink size={24} className="inline-block ml-2" />
|
||||
</P>
|
||||
<P>
|
||||
Return to the <Link href="/">dashboard</Link>
|
||||
</P>
|
||||
</div>
|
||||
|
||||
<div className="hidden md:block md:max-w-64 lg:max-w-80 w-full aspect-suqare">
|
||||
<img
|
||||
src="/images/chirpy.svg"
|
||||
alt="Chirpy the Meshtastic error"
|
||||
className="max-w-full h-auto"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<details className="mt-8 px-4 md:px-8 text-lg md:text-xl space-y-2 text-md whitespace-pre-wrap break-all">
|
||||
<summary className="cursor-pointer">Error Details</summary>
|
||||
<span className="text-sm mt-4">
|
||||
{error?.message && (
|
||||
<>
|
||||
<label htmlFor="message">Error message:</label>
|
||||
<p
|
||||
id="message"
|
||||
className="text-slate-400 break-words overflow-wrap"
|
||||
>
|
||||
{error.message}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{error?.stack && (
|
||||
<>
|
||||
<label htmlFor="stack">Stack trace:</label>
|
||||
<p
|
||||
id="stack"
|
||||
className="text-slate-400 break-words overflow-wrap"
|
||||
>
|
||||
{error.stack}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{!error?.message && !error?.stack && (
|
||||
<p className="text-slate-400">{error.toString()}</p>
|
||||
)}
|
||||
</span>
|
||||
</details>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { H4 } from "@components/UI/Typography/H4.tsx";
|
||||
import { Heading } from "../Typography/Heading";
|
||||
|
||||
export interface SidebarSectionProps {
|
||||
label: string;
|
||||
@@ -11,7 +11,9 @@ export const SidebarSection = ({
|
||||
children,
|
||||
}: SidebarSectionProps) => (
|
||||
<div className="px-4 py-2">
|
||||
<H4 className="mb-3 ml-2">{title}</H4>
|
||||
<Heading as="h4" className="mb-3 ml-2">
|
||||
{title}
|
||||
</Heading>
|
||||
<div className="space-y-1">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export interface H1Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const H1 = ({ children }: H1Props): JSX.Element => (
|
||||
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
export interface H2Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const H2 = ({ children }: H2Props): JSX.Element => (
|
||||
<h2 className="scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0 dark:border-b-slate-700">
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
export interface H3Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const H3 = ({ children }: H3Props): JSX.Element => (
|
||||
<h3 className="scroll-m-20 text-2xl font-semibold tracking-tight">
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
@@ -1,17 +0,0 @@
|
||||
import { cn } from "@app/core/utils/cn.ts";
|
||||
|
||||
export interface H4Props {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const H4 = ({ className, children }: H4Props): JSX.Element => (
|
||||
<h4
|
||||
className={cn(
|
||||
"scroll-m-20 text-xl font-semibold tracking-tight",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
@@ -1,14 +0,0 @@
|
||||
import { cn } from "@app/core/utils/cn.ts";
|
||||
|
||||
export interface H5Props {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const H5 = ({ className, children }: H5Props): JSX.Element => (
|
||||
<h5
|
||||
className={cn("scroll-m-20 text-lg font-medium tracking-tight", className)}
|
||||
>
|
||||
{children}
|
||||
</h5>
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
import type React from "react";
|
||||
|
||||
const headingStyles = {
|
||||
h1: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl",
|
||||
h2: "scroll-m-20 border-b border-b-slate-200 pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0 dark:border-b-slate-700",
|
||||
h3: "scroll-m-20 text-2xl font-semibold tracking-tight",
|
||||
h4: "scroll-m-20 text-xl font-semibold tracking-tight",
|
||||
h5: "scroll-m-20 text-lg font-medium tracking-tight",
|
||||
};
|
||||
|
||||
interface HeadingProps {
|
||||
as?: "h1" | "h2" | "h3" | "h4" | "h5";
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Heading = ({
|
||||
as: Component = "h1",
|
||||
children,
|
||||
className = "",
|
||||
...props
|
||||
}: HeadingProps) => {
|
||||
const baseStyles = headingStyles[Component] || headingStyles.h1;
|
||||
|
||||
return (
|
||||
<Component className={`${baseStyles} ${className}`} {...props}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,10 @@
|
||||
import { cn } from "@app/core/utils/cn";
|
||||
|
||||
export interface PProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const P = ({ children }: PProps): JSX.Element => (
|
||||
<p className="leading-7 not-first:mt-6">{children}</p>
|
||||
export const P = ({ children, className }: PProps) => (
|
||||
<p className={cn("leading-7 not-first:mt-6", className)}>{children}</p>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user