diff --git a/web/src/app/(app)/workflows/[workflow_id]/@runs/error.tsx b/web/src/app/(app)/workflows/[workflow_id]/@runs/error.tsx new file mode 100644 index 0000000..9df13d0 --- /dev/null +++ b/web/src/app/(app)/workflows/[workflow_id]/@runs/error.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + Card, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +// Error components must be Client Components +import { useEffect } from "react"; + +export default function Error({ + error, + reset, +}: { + error?: Error & { digest?: string }; + reset?: () => void; +}) { + useEffect(() => { + // Log the error to an error reporting service + console.log(error?.message); + }, [error]); + + return ( +
+ + + +
Something went wrong!
+
+ +
{error?.message}
+ +
+
+
+
+ ); +} diff --git a/web/src/app/(app)/workflows/[workflow_id]/layout.tsx b/web/src/app/(app)/workflows/[workflow_id]/layout.tsx index bf73308..903a3b7 100644 --- a/web/src/app/(app)/workflows/[workflow_id]/layout.tsx +++ b/web/src/app/(app)/workflows/[workflow_id]/layout.tsx @@ -1,3 +1,6 @@ +import Error from "@/app/(app)/workflows/[workflow_id]/@runs/error"; +import { ErrorBoundary } from "@/components/ErrorBoundary"; + export default async function Layout({ children, deployment, @@ -10,13 +13,15 @@ export default async function Layout({ workflow: React.ReactNode; }) { return ( -
-
- {workflow} - {deployment} + }> +
+
+ {workflow} + {deployment} +
+ {runs} + {children}
- {runs} - {children} -
+ ); } diff --git a/web/src/components/ErrorBoundary.tsx b/web/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..12115de --- /dev/null +++ b/web/src/components/ErrorBoundary.tsx @@ -0,0 +1,47 @@ +"use client"; + +import type { ErrorInfo, ReactNode } from "react"; +import { Component } from "react"; + +interface ErrorBoundaryProps { + fallback: ReactNode; + children: ReactNode; +} + +interface ErrorBoundaryState { + hasError: boolean; +} + +export class ErrorBoundary extends Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(_: Error): ErrorBoundaryState { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + componentDidCatch(error: Error, info: ErrorInfo) { + // Example "componentStack": + // in ComponentThatThrows (created by App) + // in ErrorBoundary (created by App) + // in div (created by App) + // in App + // TODO: Replace this with your own error reporting logic. + console.error("Uncaught error:", error, info); + } + + render() { + if (this.state.hasError) { + // You can render any custom fallback UI + return this.props.fallback; + } + + return this.props.children; + } +}