diff --git a/app/(main)/[locale]/calendar/[[...segments]]/page.tsx b/app/(main)/[locale]/calendar/[[...segments]]/page.tsx index 7ac1875fd..3419c08b2 100644 --- a/app/(main)/[locale]/calendar/[[...segments]]/page.tsx +++ b/app/(main)/[locale]/calendar/[[...segments]]/page.tsx @@ -1,5 +1,6 @@ import { CalendarApp } from "@/components/calendar/calendar-app"; import { generateLiteSegmentParams } from "@/lib/lite-static-params"; +import { AppErrorBoundary } from "@/components/error/app-error-boundary"; // Calendar permalinks (#733): // /calendar the stored view, today @@ -17,5 +18,9 @@ export default async function CalendarRoute({ params: Promise<{ segments?: string[] }>; }) { const { segments } = await params; - return ; + return ( + + + + ); } diff --git a/app/(main)/[locale]/contacts/[[...segments]]/page.tsx b/app/(main)/[locale]/contacts/[[...segments]]/page.tsx index 7c18db300..da9f8ae44 100644 --- a/app/(main)/[locale]/contacts/[[...segments]]/page.tsx +++ b/app/(main)/[locale]/contacts/[[...segments]]/page.tsx @@ -1,5 +1,6 @@ import { ContactsApp } from "@/components/contacts/contacts-app"; import { generateLiteSegmentParams } from "@/lib/lite-static-params"; +import { AppErrorBoundary } from "@/components/error/app-error-boundary"; // Contact permalinks (#733): /contacts/[/edit], plus /contacts/new // for the "add this sender" flow. The older query form (?contactId=, ?addEmail=) @@ -15,5 +16,9 @@ export default async function ContactsRoute({ params: Promise<{ segments?: string[] }>; }) { const { segments } = await params; - return ; + return ( + + + + ); } diff --git a/app/(main)/[locale]/error.tsx b/app/(main)/[locale]/error.tsx index e8a0caeac..f25e2ad46 100644 --- a/app/(main)/[locale]/error.tsx +++ b/app/(main)/[locale]/error.tsx @@ -2,9 +2,7 @@ import { useEffect } from "react"; import { useTranslations } from "next-intl"; -import { AlertCircle, RefreshCw, Home } from "@/components/icons"; -import { Button } from "@/components/ui/button"; -import { useRouter } from "@/i18n/navigation"; +import { ErrorState } from "@/components/error/error-fallbacks"; /** * Route-level error boundary for locale pages. @@ -18,35 +16,19 @@ export default function LocaleError({ reset: () => void; }) { const t = useTranslations("errors"); - const router = useRouter(); useEffect(() => { console.error("Route error:", error); }, [error]); return ( -
-
-
- -
-

- {t("page_error_title")} -

-

- {t("page_error_description")} -

-
- - -
-
-
+ ); } diff --git a/app/(main)/[locale]/files/[[...segments]]/page.tsx b/app/(main)/[locale]/files/[[...segments]]/page.tsx index a9782d6f6..a7a885744 100644 --- a/app/(main)/[locale]/files/[[...segments]]/page.tsx +++ b/app/(main)/[locale]/files/[[...segments]]/page.tsx @@ -1,5 +1,6 @@ import { FilesApp } from "@/components/files/files-app"; import { generateLiteSegmentParams } from "@/lib/lite-static-params"; +import { AppErrorBoundary } from "@/components/error/app-error-boundary"; // Files permalinks (#733): /files//, one path segment per // level, with ?preview= to open a file straight into the preview modal. @@ -14,5 +15,9 @@ export default async function FilesRoute({ params: Promise<{ segments?: string[] }>; }) { const { segments } = await params; - return ; + return ( + + + + ); } diff --git a/app/(main)/[locale]/settings/[[...segments]]/page.tsx b/app/(main)/[locale]/settings/[[...segments]]/page.tsx index 8c9b582d5..e4dbc45e8 100644 --- a/app/(main)/[locale]/settings/[[...segments]]/page.tsx +++ b/app/(main)/[locale]/settings/[[...segments]]/page.tsx @@ -1,5 +1,6 @@ import { SettingsApp } from "@/components/settings/settings-app"; import { generateLiteSegmentParams } from "@/lib/lite-static-params"; +import { AppErrorBoundary } from "@/components/error/app-error-boundary"; // Settings permalinks (#733): /settings/. Without a tab the page falls // back to the last tab the user had open, as before. @@ -14,5 +15,9 @@ export default async function SettingsRoute({ params: Promise<{ segments?: string[] }>; }) { const { segments } = await params; - return ; + return ( + + + + ); } diff --git a/components/error/__tests__/error-fallbacks.test.tsx b/components/error/__tests__/error-fallbacks.test.tsx new file mode 100644 index 000000000..da84607e8 --- /dev/null +++ b/components/error/__tests__/error-fallbacks.test.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +// One error fallback everywhere: the same "Try again", a way back to the +// inbox outside mail, and each app has a boundary of its own that keeps the +// navigation on screen. + +vi.mock('@/components/layout/navigation-rail', () => ({ + NavigationRail: ({ orientation }: { orientation?: string }) =>