import { useCallback, useEffect, useRef, useState } from "react"; import type { ExchangeId, PublicConfig } from "../types"; import { Card } from "./Card"; import { patchConfig } from "../config-api"; import { clsx, pct, btc } from "../format"; interface Props { config: PublicConfig; } const EXCHANGES: { id: ExchangeId; label: string }[] = [ { id: "kraken", label: "Kraken" }, { id: "bybit", label: "Bybit" }, { id: "okx", label: "OKX" }, { id: "binance", label: "Binance" }, ]; const MIN_PROFIT = 0.0001; const MAX_PROFIT = 0.01; const MIN_TRADE = 0.01; const MAX_TRADE = 1.0; const DEBOUNCE_MS = 200; function Toggle({ label, on, modified, onChange, }: { label: string; on: boolean; modified: boolean; onChange: (v: boolean) => void; }): JSX.Element { return ( ); } function SliderRow({ label, valueLabel, min, max, step, value, modified, onChange, }: { label: string; valueLabel: string; min: number; max: number; step: number; value: number; modified: boolean; onChange: (v: number) => void; }): JSX.Element { return (
{label} {valueLabel}
onChange(Number(e.target.value))} className="w-full accent-accent" />
); } export function ConfigPanel({ config }: Props): JSX.Element { const [minProfit, setMinProfit] = useState(config.minNetProfitPct); const [maxTrade, setMaxTrade] = useState(config.maxTradeBtc); const [flickerMs, setFlickerMs] = useState(config.flickerConfirmMs); const [active, setActive] = useState(config.activeExchanges); const debounceRef = useRef | null>(null); useEffect(() => { setMinProfit(config.minNetProfitPct); setMaxTrade(config.maxTradeBtc); setFlickerMs(config.flickerConfirmMs); setActive(config.activeExchanges); }, [config]); const sendPatch = useCallback((patch: Parameters[0]) => { patchConfig(patch).catch(() => { /* SSE will resync on next snapshot */ }); }, []); const debouncedPatch = useCallback( (patch: Parameters[0]) => { if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => sendPatch(patch), DEBOUNCE_MS); }, [sendPatch], ); useEffect(() => { return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, []); const defaults = config.defaults; return (
{ setMinProfit(v); debouncedPatch({ minNetProfitPct: v }); }} /> { setMaxTrade(v); debouncedPatch({ maxTradeBtc: v }); }} /> { setFlickerMs(v); debouncedPatch({ flickerConfirmMs: v }); }} />

Active exchanges

{EXCHANGES.map(({ id, label }) => ( { const next = { ...active, [id]: enabled }; setActive(next); sendPatch({ activeExchanges: { [id]: enabled } }); }} /> ))}
); }