From 2c23aa8dc1c7d7fd14018d6fdca72d8600005718 Mon Sep 17 00:00:00 2001 From: nova-rey Date: Sun, 2 Nov 2025 16:00:30 -0600 Subject: [PATCH] Fix API status helpers and clean builder lint warnings --- src/api/client.ts | 57 +++++++++++++++++++------ src/components/AboutStatus.tsx | 12 +++--- src/components/builder/IdentityForm.tsx | 4 +- src/components/builder/Navigator.tsx | 10 +++-- src/components/builder/ProfileForm.tsx | 4 +- src/components/builder/RuleForm.tsx | 4 +- src/components/builder/TableForm.tsx | 4 +- src/components/builder/VisualMap.tsx | 2 +- src/routes/Builder.tsx | 2 +- src/spec/convert.ts | 2 +- src/spec/presets.ts | 2 +- src/state/builderStore.ts | 2 +- 12 files changed, 71 insertions(+), 34 deletions(-) diff --git a/src/api/client.ts b/src/api/client.ts index f99886b..9064a39 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -25,7 +25,7 @@ export function authHeaders() { return TOKEN ? { headers: { Authorization: `Bearer ${TOKEN}` } } : {}; } -export function useMock() { +export function isMockEnabled() { return USE_MOCK; } @@ -131,19 +131,52 @@ export async function getRunReplay(id: string): Promise> } } -export async function getApiStatus(): Promise< - { ok: boolean; api_version?: string; server?: string; time?: string; endpoints?: string[] } | { ok: false; error: string } -> { +type ApiStatusSuccess = { + ok: true; + api_version?: string; + server?: string; + time?: string; + endpoints?: string[]; +} & Record; +type ApiStatusError = { ok: false; error: string }; + +export type ApiStatus = ApiStatusSuccess | ApiStatusError; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function readString(value: unknown): string | undefined { + return typeof value === "string" ? value : undefined; +} + +function readStringArray(value: unknown): string[] | undefined { + if (!Array.isArray(value)) return undefined; + const strings = value.filter((item): item is string => typeof item === "string"); + return strings.length > 0 || value.length === 0 ? strings : undefined; +} + +function toApiStatusSuccess(data: Record): ApiStatusSuccess { + const result: ApiStatusSuccess = { ok: true, ...data }; + result.api_version = readString(data.api_version); + result.server = readString(data.server); + result.time = readString(data.time); + result.endpoints = readStringArray(data.endpoints); + return result; +} + +export async function getApiStatus(): Promise { try { - if (useMock()) { + if (isMockEnabled()) { const r = await axios.get("/mock-data/status.json"); - return r.data; - } else { - const r = await axios.get(`${baseUrl()}/status`, authHeaders()); - // tolerant shape; many backends won't have /status yet - return typeof r.data === "object" && r.data ? { ok: true, ...(r.data as any) } : { ok: true }; + return isRecord(r.data) ? toApiStatusSuccess(r.data) : { ok: true }; } - } catch (e: any) { - return { ok: false, error: e?.message ?? "unknown error" }; + + const r = await axios.get(`${baseUrl()}/status`, authHeaders()); + // tolerant shape; many backends won't have /status yet + return isRecord(r.data) ? toApiStatusSuccess(r.data) : { ok: true }; + } catch (e: unknown) { + const message = e instanceof Error ? e.message : "unknown error"; + return { ok: false, error: message }; } } diff --git a/src/components/AboutStatus.tsx b/src/components/AboutStatus.tsx index 20827de..afd697e 100644 --- a/src/components/AboutStatus.tsx +++ b/src/components/AboutStatus.tsx @@ -1,15 +1,17 @@ import { useEffect, useState } from "react"; import { getApiStatus } from "../api/client"; - -type StatusOk = { ok: true; api_version?: string; server?: string; time?: string; endpoints?: string[] }; -type StatusErr = { ok: false; error: string }; +import type { ApiStatus } from "../api/client"; export default function AboutStatus() { - const [data, setData] = useState(null); + const [data, setData] = useState(null); useEffect(() => { let alive = true; - getApiStatus().then((d) => alive && setData(d as any)); + getApiStatus().then((d) => { + if (alive) { + setData(d); + } + }); return () => { alive = false; }; diff --git a/src/components/builder/IdentityForm.tsx b/src/components/builder/IdentityForm.tsx index 39b4569..aecd078 100644 --- a/src/components/builder/IdentityForm.tsx +++ b/src/components/builder/IdentityForm.tsx @@ -1,10 +1,10 @@ -import { Identity } from "../../spec/authoringTypes"; +import type { Identity } from "../../spec/authoringTypes"; export default function IdentityForm({ value, onChange, }: { value: Identity; - onChange: (v: Identity) => void; + onChange: (_value: Identity) => void; }) { return (
diff --git a/src/components/builder/Navigator.tsx b/src/components/builder/Navigator.tsx index ab08dd5..b20fe4a 100644 --- a/src/components/builder/Navigator.tsx +++ b/src/components/builder/Navigator.tsx @@ -1,13 +1,15 @@ -import { AuthoringSpec } from "../../spec/authoringTypes"; +import type { AuthoringSpec } from "../../spec/authoringTypes"; export default function Navigator(props: { spec: AuthoringSpec; selected: { kind: "identity" | "table" | "profile" | "rule"; id?: string }; - setSelected: (s: { kind: "identity" | "table" | "profile" | "rule"; id?: string }) => void; + setSelected: ( + _selection: { kind: "identity" | "table" | "profile" | "rule"; id?: string }, + ) => void; addProfile: () => void; - removeProfile: (id: string) => void; + removeProfile: (_id: string) => void; addRule: () => void; - removeRule: (id: string) => void; + removeRule: (_id: string) => void; }) { const { spec, selected } = props; return ( diff --git a/src/components/builder/ProfileForm.tsx b/src/components/builder/ProfileForm.tsx index 4797fd8..21c416d 100644 --- a/src/components/builder/ProfileForm.tsx +++ b/src/components/builder/ProfileForm.tsx @@ -1,4 +1,4 @@ -import { AuthoringSpec, BaseBet, Profile } from "../../spec/authoringTypes"; +import type { AuthoringSpec, BaseBet, Profile } from "../../spec/authoringTypes"; export default function ProfileForm({ spec: _spec, @@ -7,7 +7,7 @@ export default function ProfileForm({ }: { spec: AuthoringSpec; profile: Profile; - onChange: (p: Profile) => void; + onChange: (_profile: Profile) => void; }) { void _spec; function updateBet(idx: number, patch: Partial) { diff --git a/src/components/builder/RuleForm.tsx b/src/components/builder/RuleForm.tsx index 9c32ddc..f679438 100644 --- a/src/components/builder/RuleForm.tsx +++ b/src/components/builder/RuleForm.tsx @@ -1,4 +1,4 @@ -import { Rule, RuleVerb } from "../../spec/authoringTypes"; +import type { Rule, RuleVerb } from "../../spec/authoringTypes"; const verbs: RuleVerb[] = ["switch_profile", "press", "regress", "apply_policy"]; export default function RuleForm({ @@ -6,7 +6,7 @@ export default function RuleForm({ onChange, }: { value: Rule; - onChange: (r: Rule) => void; + onChange: (_rule: Rule) => void; }) { function setArg(k: K, v: unknown) { const args = { ...(value.then.args ?? {}), [k]: v } as Record; diff --git a/src/components/builder/TableForm.tsx b/src/components/builder/TableForm.tsx index 51fd057..bc40228 100644 --- a/src/components/builder/TableForm.tsx +++ b/src/components/builder/TableForm.tsx @@ -1,4 +1,4 @@ -import { TableSettings } from "../../spec/authoringTypes"; +import type { TableSettings } from "../../spec/authoringTypes"; const profiles = ["3-4-5x", "1x", "2x", "20x", "custom"] as const; type OddsProfile = TableSettings["odds_profile"]; @@ -7,7 +7,7 @@ export default function TableForm({ onChange, }: { value: TableSettings; - onChange: (v: TableSettings) => void; + onChange: (_value: TableSettings) => void; }) { return (
diff --git a/src/components/builder/VisualMap.tsx b/src/components/builder/VisualMap.tsx index c198e80..f0e859f 100644 --- a/src/components/builder/VisualMap.tsx +++ b/src/components/builder/VisualMap.tsx @@ -1,4 +1,4 @@ -import { AuthoringSpec } from "../../spec/authoringTypes"; +import type { AuthoringSpec } from "../../spec/authoringTypes"; export default function VisualMap({ spec }: { spec: AuthoringSpec }) { return (
diff --git a/src/routes/Builder.tsx b/src/routes/Builder.tsx index 54a725a..11a8824 100644 --- a/src/routes/Builder.tsx +++ b/src/routes/Builder.tsx @@ -10,7 +10,7 @@ import JsonPreview from "../components/builder/JsonPreview"; import ErrorList from "../components/builder/ErrorList"; import VisualMap from "../components/builder/VisualMap"; import { toDraft } from "../spec/convert"; -import { AuthoringSpec } from "../spec/authoringTypes"; +import type { AuthoringSpec } from "../spec/authoringTypes"; import { normalizeSpec } from "../api/client"; import { PRESETS } from "../spec/presets"; diff --git a/src/spec/convert.ts b/src/spec/convert.ts index 9feaa1f..dba7abf 100644 --- a/src/spec/convert.ts +++ b/src/spec/convert.ts @@ -1,4 +1,4 @@ -import { AuthoringSpec, Rule } from "./authoringTypes"; +import type { AuthoringSpec, Rule } from "./authoringTypes"; export function toDraft(spec: AuthoringSpec): Record { // Shallow clean to drop undefineds while preserving shape diff --git a/src/spec/presets.ts b/src/spec/presets.ts index f048b44..19aa0ab 100644 --- a/src/spec/presets.ts +++ b/src/spec/presets.ts @@ -1,4 +1,4 @@ -import { AuthoringSpec } from "./authoringTypes"; +import type { AuthoringSpec } from "./authoringTypes"; export const presetMolly: AuthoringSpec = { identity: { name: "3-Point Molly (Preset)", version: "0.1" }, diff --git a/src/state/builderStore.ts b/src/state/builderStore.ts index e53841a..f32adce 100644 --- a/src/state/builderStore.ts +++ b/src/state/builderStore.ts @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import { AuthoringSpec, Profile, Rule } from "../spec/authoringTypes"; +import type { AuthoringSpec, Profile, Rule } from "../spec/authoringTypes"; import { presetMolly } from "../spec/presets"; const LS_KEY = "csc_builder_workspace_v1";