SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
2.5 KB · 70 lines tsx
Raw Blame History
1"use client";23import { useEffect, useState } from "react";4import { clientApi } from "@/lib/client-api";5import { useNow } from "@/lib/stream";6import { cx } from "@/lib/format";7import type { Exchange } from "@/lib/types";89const CLOCKS: Array<{ id: string; label: string; tz: string }> = [10  { id: "xnys", label: "New York", tz: "America/New_York" },11  { id: "xlon", label: "London", tz: "Europe/London" },12  { id: "xetr", label: "Frankfurt", tz: "Europe/Berlin" },13  { id: "xtks", label: "Tokyo", tz: "Asia/Tokyo" },14  { id: "xhkg", label: "Hong Kong", tz: "Asia/Hong_Kong" },15  { id: "xasx", label: "Sydney", tz: "Australia/Sydney" },16];1718const fmts = new Map<string, Intl.DateTimeFormat>();19const timeIn = (tz: string, now: number) => {20  let f = fmts.get(tz);21  if (!f) {22    f = new Intl.DateTimeFormat("en-GB", { timeZone: tz, hour: "2-digit", minute: "2-digit", hourCycle: "h23" });23    fmts.set(tz, f);24  }25  return f.format(new Date(now));26};2728export const STATE_DOT: Record<string, string> = {29  OPEN: "bg-positive",30  PRE: "bg-warning",31  POST: "bg-warning",32  AUCTION: "bg-warning",33  HALTED: "bg-negative",34  CLOSED: "bg-stale",35  UNKNOWN: "bg-stale",36};3738/** Global clock strip: local time and session state of the main market centres (refreshed every 60 s). */39export function MarketClock({ className }: { className?: string }) {40  const now = useNow(1000);41  const [states, setStates] = useState<Record<string, string>>({});42  useEffect(() => {43    let alive = true;44    const load = () =>45      clientApi<Exchange[]>("/v1/exchanges")46        .then((xs) => alive && setStates(Object.fromEntries(xs.map((x) => [x.id, x.status?.state ?? "UNKNOWN"]))))47        .catch(() => {});48    load();49    const t = setInterval(load, 60_000);50    return () => {51      alive = false;52      clearInterval(t);53    };54  }, []);55  return (56    <div className={cx("flex items-center gap-4 overflow-x-auto whitespace-nowrap text-[11px] text-ink-3 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", className)}>57      {CLOCKS.map((c) => {58        const st = states[c.id] ?? "UNKNOWN";59        return (60          <span key={c.id} className="inline-flex items-center gap-1.5" title={`${c.label}: ${st.toLowerCase()}`}>61            <span className={cx("inline-block h-1.5 w-1.5 rounded-full", STATE_DOT[st] ?? "bg-stale", st === "OPEN" && "live-dot")} />62            <span>{c.label}</span>63            <span className="mono text-ink-2">{now ? timeIn(c.tz, now) : "--:--"}</span>64          </span>65        );66      })}67    </div>68  );69}70