From d693fc2c4696af337f8b162c36deb2d63ee3a21a Mon Sep 17 00:00:00 2001
From: pq198363-ops <246611021+pq198363-ops@users.noreply.github.com>
Date: Fri, 3 Jul 2026 23:43:05 +0800
Subject: [PATCH] feat: add about market modal
---
.../events/event-page/EventDetailsClient.tsx | 18 +++
app/components/AboutMarketModal.tsx | 107 ++++++++++++++++++
.../__tests__/AboutMarketModal.test.tsx | 31 +++++
components/ui/dialog.tsx | 4 +-
docs/about-market-modal.md | 18 +++
5 files changed, 176 insertions(+), 2 deletions(-)
create mode 100644 app/components/AboutMarketModal.tsx
create mode 100644 app/components/__tests__/AboutMarketModal.test.tsx
create mode 100644 docs/about-market-modal.md
diff --git a/app/(dashboard)/events/event-page/EventDetailsClient.tsx b/app/(dashboard)/events/event-page/EventDetailsClient.tsx
index 841063d6..4f030cb9 100644
--- a/app/(dashboard)/events/event-page/EventDetailsClient.tsx
+++ b/app/(dashboard)/events/event-page/EventDetailsClient.tsx
@@ -22,6 +22,8 @@ import { Separator } from "@/components/ui/separator";
import { Clock, DollarSign, Users, BarChart2, Loader2, Share2 } from "lucide-react";
import { formatDistanceToNowStrict, parseISO, isValid } from "date-fns";
import { MarketDetailTabs } from "@/components/market/MarketDetailTabs";
+import { ResolutionPreview } from "@/components/market/ResolutionPreview";
+import { AboutMarketModal } from "@/app/components/AboutMarketModal";
import { ShareSheet } from "@/app/components/ShareSheet";
import { useMediaQuery } from "@/hooks/use-media-query";
import {
@@ -222,6 +224,15 @@ export default function EventDetailsClient() {
: undefined;
const potentialPayout =
currentOdds && betAmount ? parseFloat(betAmount || "0") * currentOdds : 0;
+ const parsedDeadline = parseISO(eventData.deadline);
+ const deadlineLabel = isValid(parsedDeadline)
+ ? parsedDeadline.toLocaleDateString(undefined, {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ })
+ : "No deadline set";
+ const resolutionCriteria = `This market resolves after the betting deadline using verified public outcome sources. The winning outcome must match the final result described by the market premise: ${eventData.description}`;
const overviewTab = (
@@ -467,6 +478,13 @@ export default function EventDetailsClient() {
{eventData.category}
+
+
+
+
+
+
+ About this market
+
+ Review the market premise, key details, and resolution criteria.
+
+
+
+
+ Screen reader summary: {marketTitle} is a {category} market.
+ {deadlineLabel ? ` It closes on ${deadlineLabel}.` : ""}{" "}
+ {resolutionCriteria}
+
+
+
+
+
+ {category}
+ {deadlineLabel ? (
+
+
+ {deadlineLabel}
+
+ ) : null}
+
+
+
+ {marketTitle}
+
+
+ {description}
+
+
+
+
+
+
+
+
+ Resolution criteria
+
+
+
+ {resolutionCriteria}
+
+
+
+
+
+ )
+}
diff --git a/app/components/__tests__/AboutMarketModal.test.tsx b/app/components/__tests__/AboutMarketModal.test.tsx
new file mode 100644
index 00000000..59db24df
--- /dev/null
+++ b/app/components/__tests__/AboutMarketModal.test.tsx
@@ -0,0 +1,31 @@
+import React from "react"
+import { render, screen, fireEvent } from "@testing-library/react"
+import { AboutMarketModal } from "@/app/components/AboutMarketModal"
+
+const defaultProps = {
+ marketTitle: "Super Bowl Winner 2025",
+ category: "Sports",
+ description:
+ "Predict which team will win the Super Bowl LIX scheduled to be played on February 9, 2025.",
+ resolutionCriteria:
+ "The market resolves to the official championship winner after the final whistle and source verification.",
+ deadlineLabel: "Feb 9, 2025",
+}
+
+describe("AboutMarketModal", () => {
+ it("opens an accessible dialog with market context and resolution criteria", () => {
+ render()
+
+ fireEvent.click(
+ screen.getByRole("button", { name: /about this market/i })
+ )
+
+ expect(
+ screen.getByRole("dialog", { name: /about this market/i })
+ ).toBeInTheDocument()
+ expect(screen.getByText("Super Bowl Winner 2025")).toBeInTheDocument()
+ expect(screen.getByText("Sports")).toBeInTheDocument()
+ expect(screen.getAllByText(/official championship winner/i)).toHaveLength(2)
+ expect(screen.getByText(/screen reader summary/i)).toBeInTheDocument()
+ })
+})
diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx
index 1fe5c840..80142be9 100644
--- a/components/ui/dialog.tsx
+++ b/components/ui/dialog.tsx
@@ -136,7 +136,7 @@ const DialogContentWithFocusReturn = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, children, onOpenAutoFocus, ...props }, ref) => {
- const triggerRef = useRef(null);
+ const triggerRef = React.useRef(null);
// Store the trigger element when the dialog opens
const handleOpenAutoFocus = (event: Event) => {
@@ -197,4 +197,4 @@ export {
DialogFooter,
DialogTitle,
DialogDescription,
-};
\ No newline at end of file
+};
diff --git a/docs/about-market-modal.md b/docs/about-market-modal.md
new file mode 100644
index 00000000..0d558206
--- /dev/null
+++ b/docs/about-market-modal.md
@@ -0,0 +1,18 @@
+# About Market Modal
+
+The market detail page includes an `About this market` action beside the market category and share action. It opens `AboutMarketModal`, which summarizes the market premise, category, deadline, and resolution criteria without moving users away from the detail page.
+
+## Accessibility
+
+- The trigger is a keyboard-focusable button with a market-specific accessible label.
+- The dialog uses Radix Dialog for focus trapping, Escape dismissal, and focus return.
+- A screen-reader-only summary connects the dialog content to the market title, category, deadline, and resolution criteria.
+- Icons are decorative and marked with `aria-hidden`.
+
+## Verification
+
+Run the focused component test:
+
+```bash
+node node_modules/jest/bin/jest.js app/components/__tests__/AboutMarketModal.test.tsx --runInBand
+```