-
-
- {stat?.value}
-
-
- {stat?.label}
+ const isCopied = copiedIndex === index;
+ return (
+
+
+
+
+ {stat?.value}
+
+
+
+
+ {stat?.label}
+
+
-
-
- );
-}
+ );
+ }
diff --git a/components/error-boundary.tsx b/components/error-boundary.tsx
index ab05342..5ae7045 100644
--- a/components/error-boundary.tsx
+++ b/components/error-boundary.tsx
@@ -3,9 +3,15 @@
import React, { Component, ErrorInfo, ReactNode } from "react";
import { ErrorRecoveryScreen } from "@/components/error/ErrorRecoveryScreen";
+interface FallbackRenderProps {
+ error: Error;
+ incidentId: string | null;
+ resetErrorBoundary: () => void;
+}
+
interface Props {
children: ReactNode;
- fallback?: ReactNode;
+ fallback?: ReactNode | ((props: FallbackRenderProps) => ReactNode);
}
interface State {
@@ -50,6 +56,13 @@ export class ErrorBoundary extends Component
{
render() {
if (this.state.hasError) {
if (this.props.fallback) {
+ if (typeof this.props.fallback === "function") {
+ return (this.props.fallback as (props: FallbackRenderProps) => ReactNode)({
+ error: this.state.error!,
+ incidentId: this.state.incidentId,
+ resetErrorBoundary: this.handleReset,
+ });
+ }
return this.props.fallback;
}
diff --git a/src/components/BetFormErrorFallback.tsx b/src/components/BetFormErrorFallback.tsx
new file mode 100644
index 0000000..1205d6b
--- /dev/null
+++ b/src/components/BetFormErrorFallback.tsx
@@ -0,0 +1,66 @@
+"use client";
+
+import React from "react";
+import { Button } from "@/components/ui/button";
+import { AlertTriangle, RefreshCw } from "lucide-react";
+
+interface BetFormErrorFallbackProps {
+ error: Error;
+ incidentId: string | null;
+ resetErrorBoundary: () => void;
+}
+
+/**
+ * BetForm error-boundary fallback UI.
+ *
+ * Renders an attractive, card-styled fallback with a retry action when BetForm
+ * throws during render. Respects design tokens and dark mode, and exposes the
+ * incident id + error message for transparency without leaking stack traces.
+ */
+export function BetFormErrorFallback({
+ error,
+ incidentId,
+ resetErrorBoundary,
+}: BetFormErrorFallbackProps) {
+ return (
+
+
+
+
+
+
+ We couldn't load the bet form
+
+
+ An unexpected error occurred while rendering the bet form. Please try again.
+
+
+ {incidentId && (
+
+ Incident ID: {incidentId}
+
+ )}
+
+
+
+
+
+ {error?.message && (
+
+ {error.message}
+
+ )}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/BetForm.error-boundary.test.tsx b/src/pages/BetForm.error-boundary.test.tsx
new file mode 100644
index 0000000..8f80933
--- /dev/null
+++ b/src/pages/BetForm.error-boundary.test.tsx
@@ -0,0 +1,50 @@
+import React from "react";
+import { render, screen, fireEvent } from "@testing-library/react";
+import BetForm from "../BetForm";
+import { BetFormErrorFallback } from "../components/BetFormErrorFallback";
+
+describe("BetForm error boundary fallback (#773)", () => {
+ it("renders the custom fallback when a child throws", () => {
+ const spy = jest.spyOn(console, "error").mockImplementation(() => {});
+ try {
+ render(
+ {}}
+ />
+ );
+ expect(screen.getByText(/couldn't load the bet form/i)).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /Retry/i })).toBeInTheDocument();
+ expect(screen.getByText(/Incident ID: test-123/i)).toBeInTheDocument();
+ expect(screen.getByTestId("bet-form-error-msg")).toHaveTextContent("boom");
+ } finally {
+ spy.mockRestore();
+ }
+ });
+
+ it("calls resetErrorBoundary when Retry is clicked", () => {
+ const reset = jest.fn();
+ render(
+
+ );
+ fireEvent.click(screen.getByRole("button", { name: /Retry/i }));
+ expect(reset).toHaveBeenCalledTimes(1);
+ });
+
+ it("renders without incidentId when none is provided", () => {
+ render(
+
+ );
+ expect(screen.getByRole("button", { name: /Retry/i })).toBeInTheDocument();
+ expect(screen.queryByText(/Incident ID:/i)).not.toBeInTheDocument();
+ });
+});
\ No newline at end of file
diff --git a/src/pages/BetForm.tsx b/src/pages/BetForm.tsx
index 313897e..b22f3f2 100644
--- a/src/pages/BetForm.tsx
+++ b/src/pages/BetForm.tsx
@@ -4,6 +4,8 @@ import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react";
import { StellarWaveEmptyState } from "../components/EmptyState";
+import { ErrorBoundary } from "../../components/error-boundary";
+import { BetFormErrorFallback } from "../components/BetFormErrorFallback";
type FormState = "idle" | "submitting" | "success" | "error";
@@ -41,7 +43,7 @@ export default function BetForm({ campaignActive = true }: BetFormProps = {}) {
{!campaignActive ? (
) : (
- <>
+
@@ -95,7 +97,7 @@ export default function BetForm({ campaignActive = true }: BetFormProps = {}) {
)}
- >
+
)}
);
diff --git a/src/pages/NotificationBell.tooltip.test.tsx b/src/pages/NotificationBell.tooltip.test.tsx
new file mode 100644
index 0000000..a128cda
--- /dev/null
+++ b/src/pages/NotificationBell.tooltip.test.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { render, screen, fireEvent } from "@testing-library/react";
+import { NotificationBell } from "./NotificationBell";
+
+describe("NotificationBell tooltip (#763)", () => {
+ it("exposes the bell button with an accessible tooltip trigger", () => {
+ render(
);
+
+ // The bell button carries the accessible label describing the unread count
+ const bell = screen.getByRole("button", { name: /5 unread/i });
+ expect(bell).toBeInTheDocument();
+
+ // The same text is used as tooltip content, declared on the component so
+ // screen readers and hover-users get the same description.
+ expect(bell).toHaveAccessibleName("Notifications — 5 unread");
+ });
+
+ it("announces the bell state via the accessible label when there are no unread", () => {
+ render(
);
+ const bell = screen.getByRole("button", { name: /Notifications/i });
+ expect(bell).toHaveAccessibleName("Notifications");
+ });
+});
\ No newline at end of file
diff --git a/src/pages/NotificationBell.tsx b/src/pages/NotificationBell.tsx
index 52668e1..b5ab271 100644
--- a/src/pages/NotificationBell.tsx
+++ b/src/pages/NotificationBell.tsx
@@ -5,6 +5,7 @@ import { motion } from "framer-motion"
import { Bell } from "lucide-react"
import { cn } from "@/lib/utils"
import { useReducedMotion } from "@/hooks/useReducedMotion"
+import { Tooltip } from "@/app/components/Tooltip"
// ---------------------------------------------------------------------------
// Types
@@ -161,28 +162,30 @@ export function NotificationBell({
if (reducedMotion) {
return (
-
+
+
+
)
}
@@ -204,62 +207,64 @@ export function NotificationBell({
}
return (
-
-
+
- {bellIcon}
-
-
- {hasUnread ? (
-
- {badgeText}
-
- ) : null}
-
+ {bellIcon}
+
+
+ {hasUnread ? (
+
+ {badgeText}
+
+ ) : null}
+
+
)
}