spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
1import type { Metadata } from "next";2import Link from "next/link";3import { EventRow } from "@/components/market/event-row";4import { Empty, Page, PageHeader } from "@/components/ui/section";5import { api } from "@/lib/api";6import { cx, EVENT_TYPE_LABEL } from "@/lib/format";7import type { MarketEvent } from "@/lib/types";89export const metadata: Metadata = { title: "Events", description: "Canonical market events: halts, filings, price moves, volatility, market opens and source incidents.", alternates: { canonical: "/events" } };10export const dynamic = "force-dynamic";1112const TYPES = ["PRICE_CHANGE", "SESSION_HIGH", "SESSION_LOW", "VOLATILITY_SPIKE", "TRADING_HALT", "TRADING_RESUME", "FILING_PUBLISHED", "MARKET_OPEN", "MARKET_CLOSE", "SOURCE_DIVERGENCE", "SOURCE_FAILURE", "SCHEMA_DRIFT", "DOCUMENT_CHANGED"];13const SEVS = ["INFO", "NOTICE", "WARNING", "CRITICAL"];1415export default async function EventsPage({ searchParams }: { searchParams: Promise<Record<string, string | string[] | undefined>> }) {16 const sp = await searchParams;17 const type = typeof sp.type === "string" ? sp.type : "";18 const severity = typeof sp.severity === "string" ? sp.severity : "";19 const instrument = typeof sp.instrument === "string" ? sp.instrument : "";20 const country = typeof sp.country === "string" ? sp.country : "";21 const before = typeof sp.before === "string" ? sp.before : "";22 const q = new URLSearchParams({ limit: "100" });23 if (type) q.set("type", type);24 if (severity) q.set("severity", severity);25 if (instrument) q.set("instrument", instrument);26 if (country) q.set("country", country);27 if (before) q.set("before", before);28 const events = await api<MarketEvent[]>(`/v1/events?${q}`);29 const link = (patch: Record<string, string>) => {30 const u = new URLSearchParams();31 for (const [k, v] of Object.entries({ type, severity, instrument, country, ...patch })) if (v) u.set(k, v);32 const s = u.toString();33 return s ? `/events?${s}` : "/events";34 };35 const last = events[events.length - 1];36 const lastTs = last ? (typeof last.timestamp === "number" ? new Date(last.timestamp).toISOString() : String(last.timestamp).replace(" ", "T")) : null;37 return (38 <Page wide>39 <PageHeader kicker="Events" title="Market events" lead="Deduplicated, source-attributed events derived from observations (price moves, session extremes, volatility), venue feeds (halts), regulators (filings) and Market Atlas' own monitoring (source incidents)." actions={<Link href="/live" className="text-sm text-accent hover:underline">Watch live →</Link>} />40 <div className="mb-3 flex flex-wrap gap-1.5">41 <Link href={link({ type: "" })} className={cx("inline-flex h-8 items-center rounded-full border px-3 text-xs", !type ? "border-ink bg-ink text-canvas" : "border-rule text-ink-2 hover:border-rule-strong")}>42 All types43 </Link>44 {TYPES.map((t) => (45 <Link key={t} href={link({ type: t })} className={cx("inline-flex h-8 items-center rounded-full border px-3 text-xs", type === t ? "border-ink bg-ink text-canvas" : "border-rule text-ink-2 hover:border-rule-strong")}>46 {EVENT_TYPE_LABEL[t] ?? t}47 </Link>48 ))}49 </div>50 <div className="mb-4 flex flex-wrap items-center gap-1.5 text-xs">51 <span className="text-ink-3">Severity:</span>52 <Link href={link({ severity: "" })} className={cx("rounded-full border px-2.5 py-1", !severity ? "border-ink bg-ink text-canvas" : "border-rule text-ink-2")}>53 any54 </Link>55 {SEVS.map((s) => (56 <Link key={s} href={link({ severity: s })} className={cx("rounded-full border px-2.5 py-1", severity === s ? "border-ink bg-ink text-canvas" : "border-rule text-ink-2")}>57 {s.toLowerCase()}58 </Link>59 ))}60 {(instrument || country) && (61 <span className="ml-2 text-ink-3">62 filtered by {instrument ? `instrument ${instrument}` : `country ${country}`} ·{" "}63 <Link href={link({ instrument: "", country: "" })} className="text-accent hover:underline">64 clear65 </Link>66 </span>67 )}68 </div>69 {events.length ? (70 <ul className="rounded-md border border-rule bg-surface px-3">71 {events.map((e) => (72 <EventRow key={e.id} e={e} />73 ))}74 </ul>75 ) : (76 <Empty>No events match these filters.</Empty>77 )}78 {events.length >= 100 && lastTs && (79 <div className="mt-3 text-right text-xs">80 <Link href={link({ before: lastTs })} className="text-accent hover:underline">81 Older →82 </Link>83 </div>84 )}85 </Page>86 );87}88