refactor(web): restructure AITradersPage into modular architecture (#1023)

* refactor(web): restructure AITradersPage into modular architecture
Refactored the massive 2652-line AITradersPage.tsx into a clean, modular architecture following React best practices.
**Changes:**
- Decomposed 2652-line component into 12 focused modules
- Introduced Zustand stores for config and modal state management
- Extracted all business logic into useTraderActions custom hook (633 lines)
- Created reusable section components (PageHeader, TradersGrid, etc.)
- Separated complex modal logic into dedicated components
- Added TraderConfig type, eliminated all any types
- Fixed critical bugs in configuredExchanges logic and getState() usage
**File Structure:**
- Main page reduced from 2652 → 234 lines (91% reduction)
- components/traders/: 7 UI components + 5 section components
- stores/: tradersConfigStore, tradersModalStore
- hooks/: useTraderActions (all business logic)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* chore: ignore PR_DESCRIPTION.md
* fix(web): restore trader dashboard navigation functionality
Fixed missing navigation logic in refactored AITradersPage. The "查看" (View) button now correctly navigates to the trader dashboard.
**Root Cause:**
During refactoring, the `useNavigate` hook and default navigation logic were inadvertently omitted from the main page component.
**Changes:**
- Added `useNavigate` import from react-router-dom
- Implemented `handleTraderSelect` function with fallback navigation
- Restored original behavior: use `onTraderSelect` prop if provided, otherwise navigate to `/dashboard?trader=${traderId}`
**Testing:**
-  Click "查看" button navigates to trader dashboard
-  Query parameter correctly passed to dashboard
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* fix(web): correct type definitions for trader configuration
Fixed TypeScript build errors by using the correct `TraderConfigData` type instead of the incorrect `TraderConfig` type.
**Root Cause:**
During refactoring, a new `TraderConfig` type was incorrectly created that extended `CreateTraderRequest` (with fields like `name`, `ai_model_id`). However, the `TraderConfigModal` component and API responses actually use `TraderConfigData` (with fields like `trader_name`, `ai_model`).
**Changes:**
- Replaced all `TraderConfig` references with `TraderConfigData`:
  - stores/tradersModalStore.ts
  - hooks/useTraderActions.ts
  - lib/api.ts
- Removed incorrect `TraderConfig` type definition from types.ts
- Added null check for `editingTrader.trader_id` to satisfy TypeScript
**Build Status:**
-  TypeScript compilation: PASS
-  Vite production build: PASS
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
Ember
2025-11-15 12:33:48 +08:00
committed by tangmengqiu
parent 7eab056394
commit c64d4ff549
19 changed files with 3050 additions and 21 deletions

View File

@@ -0,0 +1,248 @@
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import useSWR from 'swr'
import { api } from '../lib/api'
import { useLanguage } from '../contexts/LanguageContext'
import { useAuth } from '../contexts/AuthContext'
import { useTradersConfigStore, useTradersModalStore } from '../stores'
import { useTraderActions } from '../hooks/useTraderActions'
import { TraderConfigModal } from '../components/TraderConfigModal'
import {
SignalSourceModal,
ModelConfigModal,
ExchangeConfigModal,
} from '../components/traders'
import { PageHeader } from '../components/traders/sections/PageHeader'
import { SignalSourceWarning } from '../components/traders/sections/SignalSourceWarning'
import { AIModelsSection } from '../components/traders/sections/AIModelsSection'
import { ExchangesSection } from '../components/traders/sections/ExchangesSection'
import { TradersGrid } from '../components/traders/sections/TradersGrid'
interface AITradersPageProps {
onTraderSelect?: (traderId: string) => void
}
export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
const { language } = useLanguage()
const { user, token } = useAuth()
const navigate = useNavigate()
// Zustand stores
const {
allModels,
allExchanges,
supportedModels,
supportedExchanges,
configuredModels,
configuredExchanges,
userSignalSource,
loadConfigs,
setAllModels,
setAllExchanges,
setUserSignalSource,
} = useTradersConfigStore()
const {
showCreateModal,
showEditModal,
showModelModal,
showExchangeModal,
showSignalSourceModal,
editingModel,
editingExchange,
editingTrader,
setShowCreateModal,
setShowEditModal,
setShowModelModal,
setShowExchangeModal,
setShowSignalSourceModal,
setEditingModel,
setEditingExchange,
setEditingTrader,
} = useTradersModalStore()
// SWR for traders data
const { data: traders, mutate: mutateTraders } = useSWR(
user && token ? 'traders' : null,
api.getTraders,
{ refreshInterval: 5000 }
)
// Load configurations
useEffect(() => {
loadConfigs(user, token)
}, [user, token, loadConfigs])
// Business logic hook
const {
isModelInUse,
isExchangeInUse,
handleCreateTrader,
handleEditTrader,
handleSaveEditTrader,
handleDeleteTrader,
handleToggleTrader,
handleAddModel,
handleAddExchange,
handleModelClick,
handleExchangeClick,
handleSaveModel,
handleDeleteModel,
handleSaveExchange,
handleDeleteExchange,
handleSaveSignalSource,
} = useTraderActions({
traders,
allModels,
allExchanges,
supportedModels,
supportedExchanges,
language,
mutateTraders,
setAllModels,
setAllExchanges,
setUserSignalSource,
setShowCreateModal,
setShowEditModal,
setShowModelModal,
setShowExchangeModal,
setShowSignalSourceModal,
setEditingModel,
setEditingExchange,
editingTrader,
setEditingTrader,
})
// 计算派生状态
const enabledModels = allModels?.filter((m) => m.enabled) || []
const enabledExchanges =
allExchanges?.filter((e) => {
if (!e.enabled) return false
if (e.id === 'aster') {
return e.asterUser?.trim() && e.asterSigner?.trim()
}
if (e.id === 'hyperliquid') {
return e.hyperliquidWalletAddr?.trim()
}
return true
}) || []
// 检查是否需要显示信号源警告
const showSignalWarning =
traders?.some((t) => t.use_coin_pool || t.use_oi_top) &&
!userSignalSource.coinPoolUrl &&
!userSignalSource.oiTopUrl
// 处理交易员查看
const handleTraderSelect = (traderId: string) => {
if (onTraderSelect) {
onTraderSelect(traderId)
} else {
navigate(`/dashboard?trader=${traderId}`)
}
}
return (
<div className="space-y-4 md:space-y-6 animate-fade-in">
{/* Header */}
<PageHeader
language={language}
tradersCount={traders?.length || 0}
configuredModelsCount={configuredModels.length}
configuredExchangesCount={configuredExchanges.length}
onAddModel={handleAddModel}
onAddExchange={handleAddExchange}
onConfigureSignalSource={() => setShowSignalSourceModal(true)}
onCreateTrader={() => setShowCreateModal(true)}
/>
{/* Signal Source Warning */}
{showSignalWarning && (
<SignalSourceWarning
language={language}
onConfigure={() => setShowSignalSourceModal(true)}
/>
)}
{/* Configuration Status */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
<AIModelsSection
language={language}
configuredModels={configuredModels}
isModelInUse={isModelInUse}
onModelClick={handleModelClick}
/>
<ExchangesSection
language={language}
configuredExchanges={configuredExchanges}
isExchangeInUse={isExchangeInUse}
onExchangeClick={handleExchangeClick}
/>
</div>
{/* Traders Grid */}
<TradersGrid
language={language}
traders={traders}
onTraderSelect={handleTraderSelect}
onEditTrader={handleEditTrader}
onDeleteTrader={handleDeleteTrader}
onToggleTrader={handleToggleTrader}
/>
{/* Modals */}
<TraderConfigModal
isOpen={showCreateModal}
onClose={() => setShowCreateModal(false)}
isEditMode={false}
availableModels={enabledModels}
availableExchanges={enabledExchanges}
onSave={handleCreateTrader}
/>
<TraderConfigModal
isOpen={showEditModal}
onClose={() => setShowEditModal(false)}
isEditMode={true}
traderData={editingTrader}
availableModels={enabledModels}
availableExchanges={enabledExchanges}
onSave={handleSaveEditTrader}
/>
{showModelModal && (
<ModelConfigModal
allModels={supportedModels}
configuredModels={allModels}
editingModelId={editingModel}
onSave={handleSaveModel}
onDelete={handleDeleteModel}
onClose={() => setShowModelModal(false)}
language={language}
/>
)}
{showExchangeModal && (
<ExchangeConfigModal
allExchanges={supportedExchanges}
editingExchangeId={editingExchange}
onSave={handleSaveExchange}
onDelete={handleDeleteExchange}
onClose={() => setShowExchangeModal(false)}
language={language}
/>
)}
{showSignalSourceModal && (
<SignalSourceModal
coinPoolUrl={userSignalSource.coinPoolUrl}
oiTopUrl={userSignalSource.oiTopUrl}
onSave={handleSaveSignalSource}
onClose={() => setShowSignalSourceModal(false)}
language={language}
/>
)}
</div>
)
}