From 32bf0a978e3eb13df9cd2225df1e5bf8b30701df Mon Sep 17 00:00:00 2001 From: samuel1-ona Date: Wed, 25 Mar 2026 08:12:02 +0100 Subject: [PATCH] feat: add React Error Boundaries and Sentry integration (Issue 170) --- client/app/error.tsx | 39 +++++++---- client/app/not-found.tsx | 38 ++++++----- client/components/pages/subscriptions.tsx | 80 ++++++++++++++++------- 3 files changed, 104 insertions(+), 53 deletions(-) diff --git a/client/app/error.tsx b/client/app/error.tsx index 87f02ca..e1abd4e 100644 --- a/client/app/error.tsx +++ b/client/app/error.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect } from "react"; -import { ErrorBoundary } from "@/components/ui/error-boundary"; +import * as Sentry from "@sentry/nextjs"; import { Button } from "@/components/ui/button"; export default function Error({ @@ -12,31 +12,42 @@ export default function Error({ reset: () => void; }) { useEffect(() => { - // Log error to error reporting service + // Report to Sentry + Sentry.captureException(error); console.error("Application error:", error); }, [error]); return ( -
-
-

- Something went wrong! -

-

- {error.message || "An unexpected error occurred"} -

- {error.digest && ( -

- Error ID: {error.digest} +

+
+
+

+ Something went wrong +

+

+ {error.message || "An unexpected error occurred. Our team has been notified."}

+
+ + {error.digest && ( +
+

+ Error ID: {error.digest} +

+
)} +
- diff --git a/client/app/not-found.tsx b/client/app/not-found.tsx index ce59673..0220e4f 100644 --- a/client/app/not-found.tsx +++ b/client/app/not-found.tsx @@ -1,29 +1,35 @@ +"use client"; + import Link from "next/link"; import { Button } from "@/components/ui/button"; export default function NotFound() { return ( -
-
-

- 404 -

-

- Page Not Found -

-

- The page you're looking for doesn't exist or has been moved. -

-
- -
); } - diff --git a/client/components/pages/subscriptions.tsx b/client/components/pages/subscriptions.tsx index 3c39968..2b64fd3 100644 --- a/client/components/pages/subscriptions.tsx +++ b/client/components/pages/subscriptions.tsx @@ -1,10 +1,11 @@ "use client" -import { useState, useEffect } from "react" -import { Edit2, Trash2, Mail, Clock, Copy } from "lucide-react" +import { useState, useEffect, Suspense } from "react" +import { Edit2, Trash2, Mail, Clock, Copy, AlertCircle } from "lucide-react" import { useDebounce } from "@/hooks/use-debounce" import { VirtualizedList } from "@/components/ui/virtualized-list" import { EmptyState } from "@/components/ui/empty-state" +import { ErrorBoundary } from "@/components/ui/error-boundary" interface SubscriptionsPageProps { subscriptions?: any[] @@ -277,33 +278,41 @@ export default function SubscriptionsPage({ itemHeight={80} containerHeight={600} renderItem={(sub: any, index: number) => ( - dup.subscriptions.some((s: any) => s.id === sub.id))} - unusedInfo={unusedSubscriptions.find((unused: any) => unused.id === sub.id)} - /> + } + > + dup.subscriptions.some((s: any) => s.id === sub.id))} + unusedInfo={unusedSubscriptions.find((unused: any) => unused.id === sub.id)} + /> + )} /> ) : (
{filtered.map((sub: any) => ( - dup.subscriptions.some((s: any) => s.id === sub.id))} - unusedInfo={unusedSubscriptions.find((unused: any) => unused.id === sub.id)} - /> + fallback={} + > + dup.subscriptions.some((s: any) => s.id === sub.id))} + unusedInfo={unusedSubscriptions.find((unused: any) => unused.id === sub.id)} + /> + ))}
)} @@ -445,3 +454,28 @@ function SubscriptionCard({
) } + +function BrokenCardPlaceholder({ name, darkMode }: { name?: string; darkMode?: boolean }) { + return ( +
+
+
+ +
+
+

+ {name || "Subscription"} (Error) +

+

+ This component failed to load. +

+
+
+
+

Unavailable

+
+
+ ) +}