fix(web): require active profile for simple trades

This commit is contained in:
root 2026-05-06 00:22:07 +00:00
parent feee2028aa
commit b33afc6c8c

View File

@ -5,6 +5,7 @@ import { fetchChartBars } from '../lib/marketApi';
import { getPlatformAccessToken } from '../lib/authSession';
import { tradingRuntime } from '../lib/runtime';
import { createRequestId } from '../../../shared/request-id.js';
import { fetchTradeProfiles, type TradeProfilePayload } from '../lib/profileApi';
type SimpleSide = 'buy' | 'sell';
type SimpleOrderType = 'market' | 'limit';
@ -22,6 +23,7 @@ type SimpleTradeDraft = {
};
type SimpleTradePayload = {
profile_id?: string;
symbol: string;
side: SimpleSide;
qty: number;
@ -144,6 +146,8 @@ function buildExecutionPreview(payload: SimpleTradePayload | null) {
export function SimpleView() {
const { botState } = useAppContext();
const [profiles, setProfiles] = useState<TradeProfilePayload[]>([]);
const [selectedProfileId, setSelectedProfileId] = useState('');
const [draft, setDraft] = useState<SimpleTradeDraft>(DEFAULT_DRAFT);
const [submitting, setSubmitting] = useState(false);
const [loadingPrice, setLoadingPrice] = useState(false);
@ -154,6 +158,43 @@ export function SimpleView() {
const livePrice = normalizedSymbol ? Number(botState.symbols?.[normalizedSymbol]?.price || 0) : 0;
const resolvedMarketPrice = parsePositiveNumber(draft.currentMarketPrice);
const marketPriceSource = livePrice > 0 ? 'live' : (resolvedMarketPrice !== null ? 'recent_close' : null);
const activeProfiles = useMemo(
() => profiles.filter((profile) => Boolean(profile.is_active)),
[profiles],
);
useEffect(() => {
let cancelled = false;
async function loadProfiles() {
try {
const rows = await fetchTradeProfiles();
if (cancelled) return;
setProfiles(rows);
} catch (err: any) {
if (cancelled) return;
setError(err?.message ?? 'Failed to load trade profiles');
}
}
void loadProfiles();
return () => {
cancelled = true;
};
}, []);
useEffect(() => {
if (!activeProfiles.length) {
setSelectedProfileId('');
return;
}
if (selectedProfileId && activeProfiles.some((profile) => profile.id === selectedProfileId)) {
return;
}
setSelectedProfileId(String(activeProfiles[0]?.id || ''));
}, [activeProfiles, selectedProfileId]);
useEffect(() => {
if (!livePrice) return;
@ -221,7 +262,14 @@ export function SimpleView() {
setMessage(null);
try {
if (!activeProfiles.length) {
throw new Error('No active trade profile available. Activate a profile first.');
}
const payload = buildSimpleTradePayload(draft);
if (selectedProfileId) {
payload.profile_id = selectedProfileId;
}
const accessToken = await getPlatformAccessToken();
const response = await fetch(`${tradingRuntime.tradingApiUrl}/api/trade`, {
method: 'POST',
@ -315,6 +363,23 @@ export function SimpleView() {
</select>
</label>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ fontSize: 12, fontWeight: 700, color: '#374151' }}>Profile</span>
<select
value={selectedProfileId}
onChange={e => setSelectedProfileId(e.target.value)}
disabled={!activeProfiles.length}
style={{ border: '1px solid #D1D5DB', borderRadius: 10, padding: '10px 12px', fontSize: 14, background: '#fff' }}
>
{!activeProfiles.length && <option value="">No active profile</option>}
{activeProfiles.map((profile) => (
<option key={profile.id} value={profile.id}>
{profile.name}
</option>
))}
</select>
</label>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ fontSize: 12, fontWeight: 700, color: '#374151' }}>Quantity</span>
<input
@ -435,6 +500,11 @@ export function SimpleView() {
<div style={{ color: '#111827', fontSize: 14, fontWeight: 700 }}>
{executionPreview ?? 'Complete the fields to preview the direct trade request.'}
</div>
{!activeProfiles.length && (
<div style={{ color: '#B91C1C', fontSize: 13, fontWeight: 800, marginTop: 8 }}>
No active trade profile is available. Activate one in Strategies before submitting.
</div>
)}
{payloadPreview?.sl && (
<div style={{ color: '#B91C1C', fontSize: 13, fontWeight: 800, marginTop: 8 }}>
Stop loss: {payloadPreview.sl.toFixed(4)}
@ -466,7 +536,7 @@ export function SimpleView() {
<button
type="submit"
disabled={submitting}
disabled={submitting || !activeProfiles.length}
style={{
border: 'none',
borderRadius: 999,
@ -475,7 +545,8 @@ export function SimpleView() {
padding: '12px 18px',
fontSize: 13,
fontWeight: 800,
cursor: submitting ? 'wait' : 'pointer',
cursor: submitting ? 'wait' : (!activeProfiles.length ? 'not-allowed' : 'pointer'),
opacity: !activeProfiles.length ? 0.6 : 1,
}}
>
{submitting ? 'Submitting…' : `Submit ${draft.side === 'buy' ? 'Buy' : 'Sell'}`}