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 { InstrumentTable } from "@/components/market/instrument-table";4import { Page, PageHeader, Section, Stat } from "@/components/ui/section";5import { api } from "@/lib/api";6import type { MarketsOverview } from "@/lib/types";78export const metadata: Metadata = { title: "Markets overview", description: "Indices, equities, crypto, forex, rates and commodities — one canonical, multi-source view.", alternates: { canonical: "/markets" } };9export const dynamic = "force-dynamic";1011export default async function MarketsPage() {12 const m = await api<MarketsOverview>("/v1/markets");13 const open = m.exchanges.filter((e) => e.status.state === "OPEN");14 const b = m.breadth;15 return (16 <Page wide>17 <PageHeader kicker="Markets" title="Global overview" lead="Canonical quotes across asset classes. Each table streams live where a real-time source exists; otherwise values show their delayed, at-close or end-of-day status." />18 <div className="grid grid-cols-2 gap-4 rounded-md border border-rule bg-surface px-4 py-4 sm:grid-cols-4">19 <Stat label="Venues open now" value={`${open.length} / ${m.exchanges.length}`} sub={open.slice(0, 3).map((e) => e.name.split(" ")[0]).join(", ") || "none trading"} />20 <Stat label="US equity breadth" value={b.instruments ? `${b.advancers} ▲ ${b.decliners} ▼` : "—"} sub={b.instruments ? `${b.instruments} quoted · median ${b.median_change_percent?.toFixed(2)}%` : "no session data"} tone={b.advancers > b.decliners ? "pos" : b.decliners > b.advancers ? "neg" : undefined} />21 <Stat label="Session highs / lows" value={`${b.new_session_highs} / ${b.new_session_lows}`} sub="live instruments at extremes" />22 <Stat label="Sections" value="7" sub="indices · stocks · ETFs · crypto · FX · rates · commodities" />23 </div>24 <Section title="Indices" href="/indices" hint="15-min delayed (Cboe) · at close outside the session">25 <InstrumentTable rows={m.indices} compact showExchange={false} />26 </Section>27 <div className="grid grid-cols-1 [&>*]:min-w-0 gap-6 lg:grid-cols-2">28 <Section title="Top gainers · US equities" href="/stocks?sort=-change">29 <InstrumentTable rows={m.gainers} compact />30 </Section>31 <Section title="Top losers · US equities" href="/stocks?sort=change">32 <InstrumentTable rows={m.losers} compact defaultSort="change" />33 </Section>34 </div>35 <Section title="Crypto" href="/crypto" hint="real-time consensus of public venue feeds">36 <InstrumentTable rows={m.crypto} liveClass="CRYPTO" compact showExchange />37 </Section>38 <div className="grid grid-cols-1 [&>*]:min-w-0 gap-6 lg:grid-cols-2">39 <Section title="Forex" href="/forex" hint="official reference rates">40 <InstrumentTable rows={m.forex} compact showExchange={false} defaultSort="symbol" />41 </Section>42 <Section title="Rates & yields" href="/rates" hint="percent · end of day">43 <InstrumentTable rows={m.rates} compact showExchange={false} defaultSort="symbol" />44 </Section>45 </div>46 <Section title="Commodities & futures" href="/commodities" hint="continuous front month · settlement">47 <InstrumentTable rows={m.commodities} compact showExchange={false} defaultSort="symbol" />48 </Section>49 <p className="mt-6 text-xs text-ink-3">50 Prices are aggregations of independent observations. Read the <Link href="/methodology" className="text-accent hover:underline">methodology</Link> and the <Link href="/licensing" className="text-accent hover:underline">data rights</Link> page for what each status means.51 </p>52 </Page>53 );54}55