From e0bae5a728010ff845ef620224b1b51fa2a04216 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 22 Feb 2026 19:49:27 +0100 Subject: [PATCH] feat: add graceful error recovery with user-friendly error screens and retry options for improved user experience --- CHANGELOG.md | 1 + app/error.tsx | 13 +++++++ app/notifications/error.tsx | 13 +++++++ app/org-settings/error.tsx | 13 +++++++ app/share/[id]/error.tsx | 13 +++++++ app/sites/[id]/error.tsx | 13 +++++++ app/sites/[id]/funnels/error.tsx | 13 +++++++ app/sites/[id]/realtime/error.tsx | 13 +++++++ app/sites/[id]/settings/error.tsx | 13 +++++++ app/sites/[id]/uptime/error.tsx | 13 +++++++ components/ErrorDisplay.tsx | 63 +++++++++++++++++++++++++++++++ 11 files changed, 181 insertions(+) create mode 100644 app/error.tsx create mode 100644 app/notifications/error.tsx create mode 100644 app/org-settings/error.tsx create mode 100644 app/share/[id]/error.tsx create mode 100644 app/sites/[id]/error.tsx create mode 100644 app/sites/[id]/funnels/error.tsx create mode 100644 app/sites/[id]/realtime/error.tsx create mode 100644 app/sites/[id]/settings/error.tsx create mode 100644 app/sites/[id]/uptime/error.tsx create mode 100644 components/ErrorDisplay.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 25a83af..498c533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - **Better page titles.** Browser tabs now show which site and page you're on (e.g. "Uptime · example.com | Pulse") instead of the same generic title everywhere. - **Link previews for public dashboards.** Sharing a public dashboard link on social media now shows a proper preview with the site name and description. - **Faster login redirects.** If you're not signed in and try to open a dashboard or settings page, you're redirected to login immediately instead of seeing a blank page first. Already-signed-in users who visit the login page are sent straight to the dashboard. +- **Graceful error recovery.** If a page crashes, you now see a friendly error screen with a "Try again" button instead of a blank white page. Each section of the app has its own error message so you know exactly what went wrong. ## [0.10.0-alpha] - 2026-02-21 diff --git a/app/error.tsx b/app/error.tsx new file mode 100644 index 0000000..4b1380b --- /dev/null +++ b/app/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function GlobalError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/app/notifications/error.tsx b/app/notifications/error.tsx new file mode 100644 index 0000000..8a14fd3 --- /dev/null +++ b/app/notifications/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function NotificationsError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/app/org-settings/error.tsx b/app/org-settings/error.tsx new file mode 100644 index 0000000..f9889a3 --- /dev/null +++ b/app/org-settings/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function OrgSettingsError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/app/share/[id]/error.tsx b/app/share/[id]/error.tsx new file mode 100644 index 0000000..9756b5f --- /dev/null +++ b/app/share/[id]/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function ShareError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/app/sites/[id]/error.tsx b/app/sites/[id]/error.tsx new file mode 100644 index 0000000..e1ea50a --- /dev/null +++ b/app/sites/[id]/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function DashboardError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/app/sites/[id]/funnels/error.tsx b/app/sites/[id]/funnels/error.tsx new file mode 100644 index 0000000..2be69ce --- /dev/null +++ b/app/sites/[id]/funnels/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function FunnelsError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/app/sites/[id]/realtime/error.tsx b/app/sites/[id]/realtime/error.tsx new file mode 100644 index 0000000..77bb93a --- /dev/null +++ b/app/sites/[id]/realtime/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function RealtimeError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/app/sites/[id]/settings/error.tsx b/app/sites/[id]/settings/error.tsx new file mode 100644 index 0000000..5f53d0b --- /dev/null +++ b/app/sites/[id]/settings/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function SiteSettingsError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/app/sites/[id]/uptime/error.tsx b/app/sites/[id]/uptime/error.tsx new file mode 100644 index 0000000..87bd036 --- /dev/null +++ b/app/sites/[id]/uptime/error.tsx @@ -0,0 +1,13 @@ +'use client' + +import ErrorDisplay from '@/components/ErrorDisplay' + +export default function UptimeError({ reset }: { error: Error; reset: () => void }) { + return ( + + ) +} diff --git a/components/ErrorDisplay.tsx b/components/ErrorDisplay.tsx new file mode 100644 index 0000000..6bed625 --- /dev/null +++ b/components/ErrorDisplay.tsx @@ -0,0 +1,63 @@ +'use client' + +import { Button } from '@ciphera-net/ui' + +interface ErrorDisplayProps { + title?: string + message?: string + onRetry?: () => void + onGoHome?: boolean +} + +/** + * Shared error UI for route-level error.tsx boundaries. + * Matches the visual style of the 404 page. + */ +export default function ErrorDisplay({ + title = 'Something went wrong', + message = 'An unexpected error occurred. Please try again or go back to the dashboard.', + onRetry, + onGoHome = true, +}: ErrorDisplayProps) { + return ( +
+
+
+
+
+ +
+
+ + + +
+ +

+ {title} +

+

+ {message} +

+ +
+ {onRetry && ( + + )} + {onGoHome && ( + + + + )} +
+
+
+ ) +}