Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions frontend/app/[locale]/(protected)/reports/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use client";

import { useEffect } from "react";
import { Link } from "@/i18n/navigation";

export default function ReportsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error("Reports route error:", error);
}, [error]);

return (
<main className="flex min-h-[60vh] flex-col items-center justify-center gap-4 px-4 text-center">
<h1 className="text-3xl font-bold text-gray-900">Reports unavailable</h1>
<p className="max-w-md text-sm text-gray-500">
Reports could not be loaded. Try again or return to your dashboard while other pages remain available.
</p>
<div className="flex gap-3">
<button
type="button"
onClick={() => reset()}
className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Try again
</button>
<Link
href="/"
className="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400"
>
Back to dashboard
</Link>
</div>
</main>
);
}
29 changes: 29 additions & 0 deletions frontend/test-utils/reports-error.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import ReportsError from "@/app/[locale]/(protected)/reports/error";

const mockReset = jest.fn();

jest.mock("@/i18n/navigation", () => ({
Link: ({ children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
<a {...props}>{children}</a>
),
}));

describe("ReportsError", () => {
beforeEach(() => mockReset.mockClear());

it("renders a reports-scoped error with a retry action", () => {
render(<ReportsError error={new Error("reports request failed")} reset={mockReset} />);

expect(screen.getByRole("heading", { name: "Reports unavailable" })).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Try again" }));
expect(mockReset).toHaveBeenCalledTimes(1);
});

it("provides navigation back to the dashboard", () => {
render(<ReportsError error={new Error("reports request failed")} reset={mockReset} />);

expect(screen.getByRole("link", { name: "Back to dashboard" })).toHaveAttribute("href", "/");
});
});
Loading