fix: improve frontend UX and fix OKX close position

Frontend improvements:
- Replace window.location.reload() with SWR mutate() for data refresh
- Replace native alert/confirm with toast notifications (confirmToast, notify)
- Add loading skeletons to AITradersPage and EquityChart
- Fix flash of empty state during initial load
OKX fixes:
- Fix proxy issue in Docker by using explicit no-proxy function
- Fix CloseShort sz parameter error - ensure quantity is always positive
- Fix GetPositions to return absolute value for positionAmt
This commit is contained in:
tinkle-community
2025-12-06 19:57:13 +08:00
parent a77c54dbef
commit 7bd5ca0a55
8 changed files with 132 additions and 30 deletions

View File

@@ -87,7 +87,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
oiTopUrl: '',
})
const { data: traders, mutate: mutateTraders } = useSWR<TraderInfo[]>(
const { data: traders, mutate: mutateTraders, isLoading: isTradersLoading } = useSWR<TraderInfo[]>(
user && token ? 'traders' : null,
api.getTraders,
{ refreshInterval: 5000 }
@@ -1047,7 +1047,31 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
</h2>
</div>
{traders && traders.length > 0 ? (
{isTradersLoading ? (
/* Loading Skeleton */
<div className="space-y-3 md:space-y-4">
{[1, 2, 3].map((i) => (
<div
key={i}
className="flex flex-col md:flex-row md:items-center justify-between p-3 md:p-4 rounded gap-3 md:gap-4 animate-pulse"
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
>
<div className="flex items-center gap-3 md:gap-4">
<div className="w-10 h-10 md:w-12 md:h-12 rounded-full skeleton"></div>
<div className="min-w-0 space-y-2">
<div className="skeleton h-5 w-32"></div>
<div className="skeleton h-3 w-24"></div>
</div>
</div>
<div className="flex items-center gap-3 md:gap-4">
<div className="skeleton h-6 w-16"></div>
<div className="skeleton h-6 w-16"></div>
<div className="skeleton h-8 w-20"></div>
</div>
</div>
))}
</div>
) : traders && traders.length > 0 ? (
<div className="space-y-3 md:space-y-4">
{traders.map((trader) => (
<div

View File

@@ -12,6 +12,7 @@ import {
import { api } from '../lib/api'
import { useLanguage } from '../contexts/LanguageContext'
import { t } from '../i18n/translations'
import { confirmToast } from '../lib/notify'
import { DecisionCard } from './DecisionCard'
import type {
BacktestStatusPayload,
@@ -308,12 +309,17 @@ export function BacktestPage() {
const handleDeleteRun = async () => {
if (!selectedRunId) return
if (
typeof window !== 'undefined' &&
!window.confirm(tr('toasts.confirmDelete', { id: selectedRunId }))
) {
return
}
const confirmed = await confirmToast(
tr('toasts.confirmDelete', { id: selectedRunId }),
{
title: language === 'zh' ? '确认删除' : 'Confirm Delete',
okText: language === 'zh' ? '删除' : 'Delete',
cancelText: language === 'zh' ? '取消' : 'Cancel',
}
)
if (!confirmed) return
try {
await api.deleteBacktestRun(selectedRunId)
setToast({ text: tr('toasts.deleteSuccess'), tone: 'success' })

View File

@@ -41,7 +41,7 @@ export function EquityChart({ traderId, embedded = false }: EquityChartProps) {
const { user, token } = useAuth()
const [displayMode, setDisplayMode] = useState<'dollar' | 'percent'>('dollar')
const { data: history, error } = useSWR<EquityPoint[]>(
const { data: history, error, isLoading } = useSWR<EquityPoint[]>(
user && token && traderId ? `equity-history-${traderId}` : null,
() => api.getEquityHistory(traderId),
{
@@ -61,6 +61,22 @@ export function EquityChart({ traderId, embedded = false }: EquityChartProps) {
}
)
// Loading state - show skeleton
if (isLoading) {
return (
<div className={embedded ? 'p-6' : 'binance-card p-6'}>
{!embedded && (
<h3 className="text-lg font-semibold mb-6" style={{ color: '#EAECEF' }}>
{t('accountEquityCurve', language)}
</h3>
)}
<div className="animate-pulse">
<div className="skeleton h-64 w-full rounded"></div>
</div>
</div>
)
}
if (error) {
return (
<div className={embedded ? 'p-6' : 'binance-card p-6'}>