Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 45 additions & 12 deletions src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function authHeaders() {
return TOKEN ? { headers: { Authorization: `Bearer ${TOKEN}` } } : {};
}

export function useMock() {
export function isMockEnabled() {
return USE_MOCK;
}

Expand Down Expand Up @@ -131,19 +131,52 @@ export async function getRunReplay(id: string): Promise<ApiResp<ReplayEvent[]>>
}
}

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<string, unknown>;
type ApiStatusError = { ok: false; error: string };

export type ApiStatus = ApiStatusSuccess | ApiStatusError;

function isRecord(value: unknown): value is Record<string, unknown> {
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<string, unknown>): 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<ApiStatus> {
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 };
}
}
12 changes: 7 additions & 5 deletions src/components/AboutStatus.tsx
Original file line number Diff line number Diff line change
@@ -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<StatusOk | StatusErr | null>(null);
const [data, setData] = useState<ApiStatus | null>(null);

useEffect(() => {
let alive = true;
getApiStatus().then((d) => alive && setData(d as any));
getApiStatus().then((d) => {
if (alive) {
setData(d);
}
});
return () => {
alive = false;
};
Expand Down
4 changes: 2 additions & 2 deletions src/components/builder/IdentityForm.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="space-y-2">
Expand Down
10 changes: 6 additions & 4 deletions src/components/builder/Navigator.tsx
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down
4 changes: 2 additions & 2 deletions src/components/builder/ProfileForm.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<BaseBet>) {
Expand Down
4 changes: 2 additions & 2 deletions src/components/builder/RuleForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
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({
value,
onChange,
}: {
value: Rule;
onChange: (r: Rule) => void;
onChange: (_rule: Rule) => void;
}) {
function setArg<K extends string>(k: K, v: unknown) {
const args = { ...(value.then.args ?? {}), [k]: v } as Record<string, unknown>;
Expand Down
4 changes: 2 additions & 2 deletions src/components/builder/TableForm.tsx
Original file line number Diff line number Diff line change
@@ -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"];

Expand All @@ -7,7 +7,7 @@ export default function TableForm({
onChange,
}: {
value: TableSettings;
onChange: (v: TableSettings) => void;
onChange: (_value: TableSettings) => void;
}) {
return (
<div className="grid grid-cols-2 gap-3 text-sm">
Expand Down
2 changes: 1 addition & 1 deletion src/components/builder/VisualMap.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AuthoringSpec } from "../../spec/authoringTypes";
import type { AuthoringSpec } from "../../spec/authoringTypes";
export default function VisualMap({ spec }: { spec: AuthoringSpec }) {
return (
<div className="text-sm border rounded p-2 bg-white">
Expand Down
2 changes: 1 addition & 1 deletion src/routes/Builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
2 changes: 1 addition & 1 deletion src/spec/convert.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AuthoringSpec, Rule } from "./authoringTypes";
import type { AuthoringSpec, Rule } from "./authoringTypes";

export function toDraft(spec: AuthoringSpec): Record<string, unknown> {
// Shallow clean to drop undefineds while preserving shape
Expand Down
2 changes: 1 addition & 1 deletion src/spec/presets.ts
Original file line number Diff line number Diff line change
@@ -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" },
Expand Down
2 changes: 1 addition & 1 deletion src/state/builderStore.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down