mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-06 04:20:59 +08:00
refactor(web): redesign httpClient with axios and unified error handling (#1061)
* fix(web): remove duplicate PasswordChecklist in error block
- Remove duplicate PasswordChecklist component from error message area
- Keep only the real-time password validation checklist
- Error block now displays only the error message text
Bug was introduced in commit aa0bd93 (PR #872)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* refactor(web): redesign httpClient with axios and unified error handling
Major refactoring to improve error handling architecture:
## Changes
### 1. HTTP Client Redesign (httpClient.ts)
- Replaced native fetch with axios for better interceptor support
- Implemented request/response interceptors for centralized error handling
- Added automatic Bearer token injection in request interceptor
- Network errors and system errors (404, 403, 500) now intercepted and shown via toast
- Only business logic errors (4xx except 401/403/404) returned to caller
- New ApiResponse<T> interface for type-safe responses
### 2. API Migration (api.ts)
- Migrated all 31 API methods from legacy fetch-style to new httpClient
- Updated pattern: from `res.ok/res.json()` to `result.success/result.data`
- Removed getAuthHeaders() helper (token now auto-injected)
- Added TypeScript generics for better type safety
### 3. Component Updates
- AuthContext.tsx: Updated register() to use new API
- TraderConfigModal.tsx: Migrated 3 API calls (config, templates, balance)
- RegisterPage.tsx: Simplified error display (error type handling now in API layer)
### 4. Removed Legacy Code
- Removed legacyHttpClient compatibility wrapper (~30 lines)
- Removed legacyRequest() method
- Clean separation: API layer handles all error classification
## Benefits
- Centralized error handling - no need to check network/system errors in components
- Better UX - automatic toast notifications for system errors
- Type safety - generic ApiResponse<T> provides compile-time checks
- Cleaner business components - only handle business logic errors
- Consistent error messages across the application
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
@@ -76,9 +76,9 @@ export function RegisterPage() {
|
||||
setQrCodeURL(result.qrCodeURL || '')
|
||||
setStep('setup-otp')
|
||||
} else {
|
||||
// Only business errors reach here (system/network errors shown via toast)
|
||||
const msg = result.message || t('registrationFailed', language)
|
||||
setError(msg)
|
||||
toast.error(msg)
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
@@ -298,36 +298,7 @@ export function RegisterPage() {
|
||||
color: 'var(--binance-red)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="mb-1"
|
||||
style={{ color: 'var(--brand-light-gray)' }}
|
||||
>
|
||||
{t('passwordRequirements', language)}
|
||||
</div>
|
||||
<PasswordChecklist
|
||||
rules={[
|
||||
'minLength',
|
||||
'capital',
|
||||
'lowercase',
|
||||
'number',
|
||||
'specialChar',
|
||||
'match',
|
||||
]}
|
||||
minLength={8}
|
||||
specialCharsRegex={/[@#$%!&*?]/}
|
||||
value={password}
|
||||
valueAgain={confirmPassword}
|
||||
messages={{
|
||||
minLength: t('passwordRuleMinLength', language),
|
||||
capital: t('passwordRuleUppercase', language),
|
||||
lowercase: t('passwordRuleLowercase', language),
|
||||
number: t('passwordRuleNumber', language),
|
||||
specialChar: t('passwordRuleSpecial', language),
|
||||
match: t('passwordRuleMatch', language),
|
||||
}}
|
||||
className="space-y-1"
|
||||
onChange={(isValid) => setPasswordValid(isValid)}
|
||||
/>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -115,10 +115,22 @@ export function TraderConfigModal({
|
||||
useEffect(() => {
|
||||
const fetchConfig = async () => {
|
||||
try {
|
||||
const response = await httpClient.get('/api/config')
|
||||
const config = await response.json()
|
||||
if (config.default_coins) {
|
||||
setAvailableCoins(config.default_coins)
|
||||
const result = await httpClient.get<{ default_coins?: string[] }>(
|
||||
'/api/config'
|
||||
)
|
||||
if (result.success && result.data?.default_coins) {
|
||||
setAvailableCoins(result.data.default_coins)
|
||||
} else {
|
||||
// 使用默认币种列表
|
||||
setAvailableCoins([
|
||||
'BTCUSDT',
|
||||
'ETHUSDT',
|
||||
'SOLUSDT',
|
||||
'BNBUSDT',
|
||||
'XRPUSDT',
|
||||
'DOGEUSDT',
|
||||
'ADAUSDT',
|
||||
])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch config:', error)
|
||||
@@ -141,10 +153,14 @@ export function TraderConfigModal({
|
||||
useEffect(() => {
|
||||
const fetchPromptTemplates = async () => {
|
||||
try {
|
||||
const response = await httpClient.get('/api/prompt-templates')
|
||||
const data = await response.json()
|
||||
if (data.templates) {
|
||||
setPromptTemplates(data.templates)
|
||||
const result = await httpClient.get<{ templates?: { name: string }[] }>(
|
||||
'/api/prompt-templates'
|
||||
)
|
||||
if (result.success && result.data?.templates) {
|
||||
setPromptTemplates(result.data.templates)
|
||||
} else {
|
||||
// 使用默认模板列表
|
||||
setPromptTemplates([{ name: 'default' }, { name: 'aggressive' }])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch prompt templates:', error)
|
||||
@@ -194,30 +210,26 @@ export function TraderConfigModal({
|
||||
setBalanceFetchError('')
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('auth_token')
|
||||
if (!token) {
|
||||
throw new Error('未登录,请先登录')
|
||||
const result = await httpClient.get<{
|
||||
total_equity?: number
|
||||
balance?: number
|
||||
}>(`/api/account?trader_id=${traderData.trader_id}`)
|
||||
|
||||
if (result.success && result.data) {
|
||||
// total_equity = 当前账户净值(包含未实现盈亏)
|
||||
// 这应该作为新的初始余额
|
||||
const currentBalance =
|
||||
result.data.total_equity || result.data.balance || 0
|
||||
|
||||
setFormData((prev) => ({ ...prev, initial_balance: currentBalance }))
|
||||
toast.success('已获取当前余额')
|
||||
} else {
|
||||
throw new Error(result.message || '获取余额失败')
|
||||
}
|
||||
|
||||
const response = await httpClient.get(
|
||||
`/api/account?trader_id=${traderData.trader_id}`,
|
||||
{
|
||||
Authorization: `Bearer ${token}`,
|
||||
}
|
||||
)
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
// total_equity = 当前账户净值(包含未实现盈亏)
|
||||
// 这应该作为新的初始余额
|
||||
const currentBalance = data.total_equity || data.balance || 0
|
||||
|
||||
setFormData((prev) => ({ ...prev, initial_balance: currentBalance }))
|
||||
toast.success('已获取当前余额')
|
||||
} catch (error) {
|
||||
console.error('获取余额失败:', error)
|
||||
setBalanceFetchError('获取余额失败,请检查网络连接')
|
||||
toast.error('获取余额失败,请检查网络连接')
|
||||
// Note: Network/system errors already shown via toast by httpClient
|
||||
} finally {
|
||||
setIsFetchingBalance(false)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user