diff --git a/package-lock.json b/package-lock.json index 44519ab..14f7d17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5984,6 +5984,16 @@ "url": "https://github.com/steveukx/git-js?sponsor=1" } }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6725,6 +6735,7 @@ "lucide-react": "^0.563.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.4.0" }, "devDependencies": { diff --git a/packages/web/package.json b/packages/web/package.json index a4798be..e1dcd14 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -22,6 +22,7 @@ "lucide-react": "^0.563.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.4.0" }, "devDependencies": { diff --git a/packages/web/src/components/ErrorBoundary.tsx b/packages/web/src/components/ErrorBoundary.tsx new file mode 100644 index 0000000..2f4134f --- /dev/null +++ b/packages/web/src/components/ErrorBoundary.tsx @@ -0,0 +1,54 @@ +import { Component } from "react"; +import type { ErrorInfo, ReactNode } from "react"; +import { AlertCircle } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +interface ErrorBoundaryProps { + children: ReactNode; +} + +interface ErrorBoundaryState { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo): void { + console.error("ErrorBoundary caught an error:", error, errorInfo); + } + + handleReload = () => { + window.location.reload(); + }; + + render() { + if (this.state.hasError) { + return ( +
+ +

Something went wrong

+

+ {this.state.error?.message ?? "An unexpected error occurred."} +

+ +
+ ); + } + + return this.props.children; + } +} diff --git a/packages/web/src/components/ui/sonner.tsx b/packages/web/src/components/ui/sonner.tsx new file mode 100644 index 0000000..61522f6 --- /dev/null +++ b/packages/web/src/components/ui/sonner.tsx @@ -0,0 +1,13 @@ +import { Toaster as SonnerToaster } from "sonner"; + +export function Toaster() { + return ( + + ); +} diff --git a/packages/web/src/routes/__root.tsx b/packages/web/src/routes/__root.tsx index e995d87..c2bcc3d 100644 --- a/packages/web/src/routes/__root.tsx +++ b/packages/web/src/routes/__root.tsx @@ -1,16 +1,27 @@ -import { createRootRoute, Outlet } from '@tanstack/react-router' +import { createRootRoute, Link, Outlet } from '@tanstack/react-router' import { AppLayout } from '../layouts/AppLayout' +import { ErrorBoundary } from '../components/ErrorBoundary' +import { Toaster } from '../components/ui/sonner' +import { Button } from '../components/ui/button' export const Route = createRootRoute({ component: () => ( - - - + <> + + + + + + + ), notFoundComponent: () => ( -
+

Page not found

-

The page you are looking for does not exist.

+

The page you are looking for does not exist.

+
), })