Skip to content

Commit 9a31357

Browse files
committed
Improve error handling for order execution failures
Introduces a reusable extractErrorMessage utility to provide more informative error messages in toasts when order execution fails. Updates RecentTrades, TradeHistoryTable, and useOrderActions to use this function for both API and exception errors. Also updates Header to refresh running analyses count on route changes and corrects the interval timing.
1 parent e060bd9 commit 9a31357

File tree

4 files changed

+110
-10
lines changed

4 files changed

+110
-10
lines changed

src/components/Header.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Link, useNavigate } from "react-router-dom";
1+
import { Link, useNavigate, useLocation } from "react-router-dom";
22
import { useState, useEffect, useMemo } from "react";
33
import { Button } from "@/components/ui/button";
44
import {
@@ -36,6 +36,7 @@ import {
3636

3737
export default function Header() {
3838
const navigate = useNavigate();
39+
const location = useLocation();
3940
const { user, profile, isAuthenticated, logout, apiSettings } = useAuth();
4041
const { getPrimaryRole, isLoading: isRoleLoading } = useRBAC();
4142
const [runningAnalyses, setRunningAnalyses] = useState(0);
@@ -112,10 +113,10 @@ export default function Header() {
112113
};
113114

114115
checkRunningTasks();
115-
const interval = setInterval(checkRunningTasks, 10000); // Check every 10 seconds
116+
const interval = setInterval(checkRunningTasks, 100000); // Check every 10 seconds
116117

117118
return () => clearInterval(interval);
118-
}, [user]);
119+
}, [user, location.pathname]);
119120

120121
return (
121122
<div className="sticky top-0 z-50">

src/components/RecentTrades.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,38 @@ interface TradeDecision {
3636
createdAt: string;
3737
}
3838

39+
const extractErrorMessage = (value: unknown): string | null => {
40+
if (!value) return null;
41+
42+
if (typeof value === 'string') {
43+
return value;
44+
}
45+
46+
if (value instanceof Error) {
47+
return value.message;
48+
}
49+
50+
if (typeof value === 'object') {
51+
const record = value as Record<string, unknown>;
52+
const candidateKeys = ['errorDetail', 'error', 'message', 'warning', 'details', 'reason'] as const;
53+
54+
for (const key of candidateKeys) {
55+
const nested = record[key];
56+
if (!nested || nested === value) continue;
57+
const extracted = extractErrorMessage(nested);
58+
if (extracted) {
59+
return extracted;
60+
}
61+
}
62+
63+
if (record.code !== undefined) {
64+
return String(record.code);
65+
}
66+
}
67+
68+
return null;
69+
};
70+
3971
function RecentTrades() {
4072
const [loading, setLoading] = useState(true);
4173
const [allTrades, setAllTrades] = useState<TradeDecision[]>([]);
@@ -356,17 +388,18 @@ function RecentTrades() {
356388
// Refresh trades
357389
fetchAllTrades();
358390
} else {
391+
const errorDescription = extractErrorMessage(data) || "Failed to execute order";
359392
toast({
360393
title: "Order Failed",
361-
description: data.message || "Failed to execute order",
394+
description: errorDescription,
362395
variant: "destructive",
363396
});
364397
}
365398
} catch (err: any) {
366399
console.error('Error executing order:', err);
367400
toast({
368401
title: "Order Failed",
369-
description: err.message || 'Failed to execute order on Alpaca',
402+
description: extractErrorMessage(err) || 'Failed to execute order on Alpaca',
370403
variant: "destructive"
371404
});
372405
} finally {

src/components/TradeHistoryTable.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,38 @@ interface RebalanceGroup {
4545
trades: TradeDecision[];
4646
}
4747

48+
const extractErrorMessage = (value: unknown): string | null => {
49+
if (!value) return null;
50+
51+
if (typeof value === 'string') {
52+
return value;
53+
}
54+
55+
if (value instanceof Error) {
56+
return value.message;
57+
}
58+
59+
if (typeof value === 'object') {
60+
const record = value as Record<string, unknown>;
61+
const candidateKeys = ['errorDetail', 'error', 'message', 'warning', 'details', 'reason'] as const;
62+
63+
for (const key of candidateKeys) {
64+
const nested = record[key];
65+
if (!nested || nested === value) continue;
66+
const extracted = extractErrorMessage(nested);
67+
if (extracted) {
68+
return extracted;
69+
}
70+
}
71+
72+
if (record.code !== undefined) {
73+
return String(record.code);
74+
}
75+
}
76+
77+
return null;
78+
};
79+
4880
export default function TradeHistoryTable() {
4981
const [loading, setLoading] = useState(true);
5082
const [refreshing, setRefreshing] = useState(false); // Separate state for refresh button
@@ -429,17 +461,18 @@ export default function TradeHistoryTable() {
429461
// Refresh trades
430462
fetchAllTrades(false);
431463
} else {
464+
const errorDescription = extractErrorMessage(data) || "Failed to execute order";
432465
toast({
433466
title: "Order Failed",
434-
description: data.message || "Failed to execute order",
467+
description: errorDescription,
435468
variant: "destructive",
436469
});
437470
}
438471
} catch (err: any) {
439472
console.error('Error executing order:', err);
440473
toast({
441474
title: "Order Failed",
442-
description: err.message || 'Failed to execute order on Alpaca',
475+
description: extractErrorMessage(err) || 'Failed to execute order on Alpaca',
443476
variant: "destructive"
444477
});
445478
} finally {

src/components/analysis-detail/hooks/useOrderActions.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,38 @@ interface UseOrderActionsProps {
1717
updateAnalysisData: (updates: Partial<any>) => void;
1818
}
1919

20+
const extractErrorMessage = (value: unknown): string | null => {
21+
if (!value) return null;
22+
23+
if (typeof value === 'string') {
24+
return value;
25+
}
26+
27+
if (value instanceof Error) {
28+
return value.message;
29+
}
30+
31+
if (typeof value === 'object') {
32+
const record = value as Record<string, unknown>;
33+
const candidateKeys = ['errorDetail', 'error', 'message', 'warning', 'details', 'reason'] as const;
34+
35+
for (const key of candidateKeys) {
36+
const nested = record[key];
37+
if (!nested || nested === value) continue;
38+
const extracted = extractErrorMessage(nested);
39+
if (extracted) {
40+
return extracted;
41+
}
42+
}
43+
44+
if (record.code !== undefined) {
45+
return String(record.code);
46+
}
47+
}
48+
49+
return null;
50+
};
51+
2052
export function useOrderActions({ analysisData, updateAnalysisData }: UseOrderActionsProps) {
2153
const { user } = useAuth();
2254
const { toast } = useToast();
@@ -157,9 +189,10 @@ export function useOrderActions({ analysisData, updateAnalysisData }: UseOrderAc
157189
pollAlpacaOrderStatus(data.alpacaOrderId);
158190
}
159191
} else {
192+
const errorDescription = extractErrorMessage(data) || "Failed to execute order";
160193
toast({
161194
title: "Order Failed",
162-
description: data.message || "Failed to execute order",
195+
description: errorDescription,
163196
variant: "destructive",
164197
});
165198
}
@@ -173,7 +206,7 @@ export function useOrderActions({ analysisData, updateAnalysisData }: UseOrderAc
173206
});
174207
toast({
175208
title: "Order Failed",
176-
description: error.message || "Failed to execute order on Alpaca",
209+
description: extractErrorMessage(error) || "Failed to execute order on Alpaca",
177210
variant: "destructive",
178211
});
179212
} finally {
@@ -244,7 +277,7 @@ export function useOrderActions({ analysisData, updateAnalysisData }: UseOrderAc
244277
});
245278
toast({
246279
title: "Error",
247-
description: error.message || "Failed to reject order",
280+
description: extractErrorMessage(error) || "Failed to reject order",
248281
variant: "destructive",
249282
});
250283
} finally {

0 commit comments

Comments
 (0)