evOo-local/apps/web/src/components/TradingModeIndicator.tsx
cynarAI de2a64f070 fix(web): read sidebar trading mode from the bot status API (#80)
The Live Trading control stored its mode in localStorage, so the shell could
claim live money while the bot was in dry-run. Show the Tradingbot status
when TRADING_BOT_URL is set, and an unknown state when it is not.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-09-22 22:10:18 +02:00

96 lines
2.6 KiB
TypeScript

import { Trans } from "@lingui/react/macro";
import { cn } from "@rakazo/ui-web";
import { Activity } from "lucide-react";
import type { ReactNode } from "react";
import {
presentTradingStatus,
type TradingBotUiState,
type TradingModeLabelId,
type TradingModeSubtitle,
} from "../lib/trading-bot-status";
export function TradingModeIndicator({
snapshot,
className,
}: {
snapshot: TradingBotUiState;
className?: string;
}) {
const presentation = presentTradingStatus(snapshot);
return (
<div
role="status"
aria-live="polite"
aria-busy={presentation.label === "loading"}
data-testid="trading-mode-status"
data-trading-state={presentation.label}
className={cn("flex min-w-0 items-center gap-3 rounded-xl px-2.5 py-2", className)}
>
<span
className={cn(
"grid h-[30px] w-[30px] shrink-0 place-items-center rounded-lg",
presentation.tone === "live"
? "bg-warning/10 text-warning"
: "bg-muted text-muted-foreground",
)}
>
<Activity
strokeWidth={1.8}
aria-hidden="true"
className={cn("size-icon-md", presentation.pulse && "animate-pulse")}
/>
</span>
<span className="min-w-0">
<span className="block truncate text-ui-body font-medium text-foreground/90">
<TradingModeLabel label={presentation.label} />
</span>
{presentation.subtitle ? (
<span className="block truncate text-ui-body-sm text-muted-foreground/70">
<TradingModeSubtitleText subtitle={presentation.subtitle} />
</span>
) : null}
</span>
</div>
);
}
function TradingModeLabel({ label }: { label: TradingModeLabelId }): ReactNode {
switch (label) {
case "loading":
return <Trans>Trading</Trans>;
case "unconfigured":
return <Trans>Not configured</Trans>;
case "offline":
return <Trans>Offline</Trans>;
case "unknown":
return <Trans>Unknown</Trans>;
case "live":
return <Trans>Live Trading</Trans>;
case "paper":
return <Trans>Paper</Trans>;
case "dry_run":
return <Trans>Dry run</Trans>;
default: {
const exhaustive: never = label;
return exhaustive;
}
}
}
function TradingModeSubtitleText({ subtitle }: { subtitle: TradingModeSubtitle }): ReactNode {
switch (subtitle.type) {
case "error":
return subtitle.text;
case "adapters":
return subtitle.count === 1 ? (
<Trans>1 adapter</Trans>
) : (
<Trans>{subtitle.count} adapters</Trans>
);
default: {
const exhaustive: never = subtitle;
return exhaustive;
}
}
}