fix(web): require active profile for simple trades
This commit is contained in:
parent
feee2028aa
commit
b33afc6c8c
@ -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'}`}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user