mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-10 22:36:58 +08:00
Merge branch 'tinkle-nofx/dev' with conflict resolution
- Resolve UI layout conflicts in App.tsx - Keep modern Binance-style header with authentication logic - Maintain responsive design and user interface improvements 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
259
web/src/App.tsx
259
web/src/App.tsx
@@ -13,6 +13,7 @@ import { LanguageProvider, useLanguage } from './contexts/LanguageContext';
|
|||||||
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
||||||
import { t, type Language } from './i18n/translations';
|
import { t, type Language } from './i18n/translations';
|
||||||
import { useSystemConfig } from './hooks/useSystemConfig';
|
import { useSystemConfig } from './hooks/useSystemConfig';
|
||||||
|
import { Zap } from 'lucide-react';
|
||||||
import type {
|
import type {
|
||||||
SystemStatus,
|
SystemStatus,
|
||||||
AccountInfo,
|
AccountInfo,
|
||||||
@@ -264,44 +265,230 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen" style={{ background: 'var(--brand-black)', color: 'var(--brand-light-gray)' }}>
|
<div className="min-h-screen" style={{ background: '#0B0E11', color: '#EAECEF' }}>
|
||||||
<HeaderBar
|
{/* Header - Binance Style */}
|
||||||
|
<header className="glass sticky top-0 z-50 backdrop-blur-xl">
|
||||||
isLoggedIn={!!user}
|
<div className="max-w-[1920px] mx-auto px-3 md:px-6 py-4">
|
||||||
isHomePage={false}
|
{/* Desktop Layout */}
|
||||||
currentPage={currentPage}
|
<div className="hidden md:flex relative items-center">
|
||||||
language={language}
|
{/* Left - Logo and Title */}
|
||||||
onLanguageChange={setLanguage}
|
<div className="flex items-center gap-3">
|
||||||
user={user}
|
<div className="w-8 h-8 flex items-center justify-center">
|
||||||
onLogout={logout}
|
<img src="/icons/nofx.svg?v=2" alt="NOFX" className="w-8 h-8" />
|
||||||
isAdminMode={systemConfig?.admin_mode}
|
</div>
|
||||||
onPageChange={(page) => {
|
<div>
|
||||||
console.log('App.tsx onPageChange called with:', page);
|
<h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
|
||||||
console.log('Current route:', route, 'Current page:', currentPage);
|
{t('appTitle', language)}
|
||||||
|
</h1>
|
||||||
if (page === 'competition') {
|
<p className="text-xs mono" style={{ color: '#848E9C' }}>
|
||||||
console.log('Navigating to competition');
|
{t('subtitle', language)}
|
||||||
window.history.pushState({}, '', '/competition');
|
</p>
|
||||||
setRoute('/competition');
|
</div>
|
||||||
setCurrentPage('competition');
|
</div>
|
||||||
} else if (page === 'traders') {
|
|
||||||
console.log('Navigating to traders');
|
{/* Center - Page Toggle (absolutely positioned) */}
|
||||||
window.history.pushState({}, '', '/traders');
|
<div className="absolute left-1/2 transform -translate-x-1/2 flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
||||||
setRoute('/traders');
|
<button
|
||||||
setCurrentPage('traders');
|
onClick={() => setCurrentPage('competition')}
|
||||||
} else if (page === 'trader') {
|
className={`px-3 py-2 rounded text-sm font-semibold transition-all`}
|
||||||
console.log('Navigating to trader/dashboard');
|
style={currentPage === 'competition'
|
||||||
window.history.pushState({}, '', '/dashboard');
|
? { background: '#F0B90B', color: '#000' }
|
||||||
setRoute('/dashboard');
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
setCurrentPage('trader');
|
}
|
||||||
}
|
>
|
||||||
|
{t('aiCompetition', language)}
|
||||||
console.log('After navigation - route:', route, 'currentPage:', currentPage);
|
</button>
|
||||||
}}
|
<button
|
||||||
/>
|
onClick={() => setCurrentPage('traders')}
|
||||||
|
className={`px-3 py-2 rounded text-sm font-semibold transition-all`}
|
||||||
|
style={currentPage === 'traders'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('aiTraders', language)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage('trader')}
|
||||||
|
className={`px-3 py-2 rounded text-sm font-semibold transition-all`}
|
||||||
|
style={currentPage === 'trader'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('tradingPanel', language)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right - Actions */}
|
||||||
|
<div className="ml-auto flex items-center gap-3">
|
||||||
|
|
||||||
|
{/* User Info - Only show if not in admin mode */}
|
||||||
|
{!systemConfig?.admin_mode && user && (
|
||||||
|
<div className="flex items-center gap-2 px-3 py-2 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||||
|
<div className="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold" style={{ background: '#F0B90B', color: '#000' }}>
|
||||||
|
{user.email[0].toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<span className="text-sm" style={{ color: '#EAECEF' }}>{user.email}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Admin Mode Indicator */}
|
||||||
|
{systemConfig?.admin_mode && (
|
||||||
|
<div className="flex items-center gap-2 px-3 py-2 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||||
|
<Zap className="w-4 h-4" style={{ color: '#F0B90B' }} />
|
||||||
|
<span className="text-sm font-semibold" style={{ color: '#F0B90B' }}>{t('adminMode', language)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Language Toggle */}
|
||||||
|
<div className="flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => setLanguage('zh')}
|
||||||
|
className="px-3 py-1.5 rounded text-xs font-semibold transition-all"
|
||||||
|
style={language === 'zh'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
中文
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setLanguage('en')}
|
||||||
|
className="px-3 py-1.5 rounded text-xs font-semibold transition-all"
|
||||||
|
style={language === 'en'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
EN
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Logout Button - Only show if not in admin mode */}
|
||||||
|
{!systemConfig?.admin_mode && (
|
||||||
|
<button
|
||||||
|
onClick={logout}
|
||||||
|
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||||
|
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D', border: '1px solid rgba(246, 70, 93, 0.2)' }}
|
||||||
|
>
|
||||||
|
{t('logout', language)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Layout */}
|
||||||
|
<div className="flex md:hidden flex-col gap-3">
|
||||||
|
{/* Top Row - Logo, Title and Language */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img src="/icons/nofx.svg?v=2" alt="NOFX" className="w-7 h-7" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-base font-bold" style={{ color: '#EAECEF' }}>
|
||||||
|
{t('appTitle', language)}
|
||||||
|
</h1>
|
||||||
|
<p className="text-xs mono" style={{ color: '#848E9C' }}>
|
||||||
|
{t('subtitle', language)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Language Toggle - Right side on mobile */}
|
||||||
|
<div className="flex gap-1 rounded p-0.5" style={{ background: '#1E2329' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => setLanguage('zh')}
|
||||||
|
className="px-2 py-1 rounded text-xs font-semibold transition-all"
|
||||||
|
style={language === 'zh'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
中文
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setLanguage('en')}
|
||||||
|
className="px-2 py-1 rounded text-xs font-semibold transition-all"
|
||||||
|
style={language === 'en'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
EN
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Second Row - Page Toggle */}
|
||||||
|
<div className="flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage('competition')}
|
||||||
|
className={`flex-1 px-2 py-1.5 rounded text-xs font-semibold transition-all`}
|
||||||
|
style={currentPage === 'competition'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('aiCompetition', language)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage('traders')}
|
||||||
|
className={`flex-1 px-2 py-1.5 rounded text-xs font-semibold transition-all`}
|
||||||
|
style={currentPage === 'traders'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('aiTraders', language)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentPage('trader')}
|
||||||
|
className={`flex-1 px-2 py-1.5 rounded text-xs font-semibold transition-all`}
|
||||||
|
style={currentPage === 'trader'
|
||||||
|
? { background: '#F0B90B', color: '#000' }
|
||||||
|
: { background: 'transparent', color: '#848E9C' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('tradingPanel', language)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Third Row - User Info and Logout */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{/* User Info or Admin Mode */}
|
||||||
|
{!systemConfig?.admin_mode && user && (
|
||||||
|
<div className="flex-1 flex items-center gap-2 px-2 py-1.5 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||||
|
<div className="w-5 h-5 rounded-full flex items-center justify-center text-xs font-bold" style={{ background: '#F0B90B', color: '#000' }}>
|
||||||
|
{user.email[0].toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs truncate" style={{ color: '#EAECEF' }}>{user.email}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{systemConfig?.admin_mode && (
|
||||||
|
<div className="flex-1 flex items-center gap-2 px-2 py-1.5 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||||
|
<Zap className="w-4 h-4" style={{ color: '#F0B90B' }} />
|
||||||
|
<span className="text-xs font-semibold" style={{ color: '#F0B90B' }}>{t('adminMode', language)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Logout Button */}
|
||||||
|
{!systemConfig?.admin_mode && (
|
||||||
|
<button
|
||||||
|
onClick={logout}
|
||||||
|
className="px-3 py-1.5 rounded text-xs font-semibold transition-all"
|
||||||
|
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D', border: '1px solid rgba(246, 70, 93, 0.2)' }}
|
||||||
|
>
|
||||||
|
{t('logout', language)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<main className="max-w-[1920px] mx-auto px-6 py-6 pt-24">
|
<main className="max-w-[1920px] mx-auto px-3 md:px-6 py-4 md:py-6">
|
||||||
{currentPage === 'competition' ? (
|
{currentPage === 'competition' ? (
|
||||||
<CompetitionPage />
|
<CompetitionPage />
|
||||||
) : currentPage === 'traders' ? (
|
) : currentPage === 'traders' ? (
|
||||||
|
|||||||
@@ -458,22 +458,22 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 animate-fade-in">
|
<div className="space-y-4 md:space-y-6 animate-fade-in">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 md:gap-0">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-3 md:gap-4">
|
||||||
<div className="w-12 h-12 rounded-xl flex items-center justify-center" style={{
|
<div className="w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center" style={{
|
||||||
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
|
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
|
||||||
boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)'
|
boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)'
|
||||||
}}>
|
}}>
|
||||||
<Bot className="w-6 h-6" style={{ color: '#000' }} />
|
<Bot className="w-5 h-5 md:w-6 md:h-6" style={{ color: '#000' }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
<h1 className="text-xl md:text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||||
{t('aiTraders', language)}
|
{t('aiTraders', language)}
|
||||||
<span className="text-xs font-normal px-2 py-1 rounded" style={{
|
<span className="text-xs font-normal px-2 py-1 rounded" style={{
|
||||||
background: 'rgba(240, 185, 11, 0.15)',
|
background: 'rgba(240, 185, 11, 0.15)',
|
||||||
color: '#F0B90B'
|
color: '#F0B90B'
|
||||||
}}>
|
}}>
|
||||||
{traders?.length || 0} {t('active', language)}
|
{traders?.length || 0} {t('active', language)}
|
||||||
</span>
|
</span>
|
||||||
@@ -483,37 +483,37 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-2 md:gap-3 w-full md:w-auto overflow-x-auto flex-wrap md:flex-nowrap">
|
||||||
<button
|
<button
|
||||||
onClick={handleAddModel}
|
onClick={handleAddModel}
|
||||||
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105 flex items-center gap-2"
|
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1 md:gap-2 whitespace-nowrap"
|
||||||
style={{
|
style={{
|
||||||
background: '#2B3139',
|
background: '#2B3139',
|
||||||
color: '#EAECEF',
|
color: '#EAECEF',
|
||||||
border: '1px solid #474D57'
|
border: '1px solid #474D57'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
{t('aiModels', language)}
|
{t('aiModels', language)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleAddExchange}
|
onClick={handleAddExchange}
|
||||||
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105 flex items-center gap-2"
|
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1 md:gap-2 whitespace-nowrap"
|
||||||
style={{
|
style={{
|
||||||
background: '#2B3139',
|
background: '#2B3139',
|
||||||
color: '#EAECEF',
|
color: '#EAECEF',
|
||||||
border: '1px solid #474D57'
|
border: '1px solid #474D57'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
{t('exchanges', language)}
|
{t('exchanges', language)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowSignalSourceModal(true)}
|
onClick={() => setShowSignalSourceModal(true)}
|
||||||
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 whitespace-nowrap"
|
||||||
style={{
|
style={{
|
||||||
background: '#2B3139',
|
background: '#2B3139',
|
||||||
color: '#EAECEF',
|
color: '#EAECEF',
|
||||||
@@ -522,11 +522,11 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
>
|
>
|
||||||
📡 {t('signalSource', language)}
|
📡 {t('signalSource', language)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowCreateModal(true)}
|
onClick={() => setShowCreateModal(true)}
|
||||||
disabled={configuredModels.length === 0 || configuredExchanges.length === 0}
|
disabled={configuredModels.length === 0 || configuredExchanges.length === 0}
|
||||||
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1 md:gap-2 whitespace-nowrap"
|
||||||
style={{
|
style={{
|
||||||
background: (configuredModels.length > 0 && configuredExchanges.length > 0) ? '#F0B90B' : '#2B3139',
|
background: (configuredModels.length > 0 && configuredExchanges.length > 0) ? '#F0B90B' : '#2B3139',
|
||||||
color: (configuredModels.length > 0 && configuredExchanges.length > 0) ? '#000' : '#848E9C'
|
color: (configuredModels.length > 0 && configuredExchanges.length > 0) ? '#000' : '#848E9C'
|
||||||
@@ -539,30 +539,30 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Configuration Status */}
|
{/* Configuration Status */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
|
||||||
{/* AI Models */}
|
{/* AI Models */}
|
||||||
<div className="binance-card p-4">
|
<div className="binance-card p-3 md:p-4">
|
||||||
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
<h3 className="text-base md:text-lg font-semibold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||||
<Brain className="w-5 h-5" style={{ color: '#60a5fa' }} />
|
<Brain className="w-4 h-4 md:w-5 md:h-5" style={{ color: '#60a5fa' }} />
|
||||||
{t('aiModels', language)}
|
{t('aiModels', language)}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-2 md:space-y-3">
|
||||||
{configuredModels.map(model => {
|
{configuredModels.map(model => {
|
||||||
const inUse = isModelInUse(model.id);
|
const inUse = isModelInUse(model.id);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={model.id}
|
key={model.id}
|
||||||
className={`flex items-center justify-between p-3 rounded transition-all ${
|
className={`flex items-center justify-between p-2 md:p-3 rounded transition-all ${
|
||||||
inUse ? 'cursor-not-allowed' : 'cursor-pointer hover:bg-gray-700'
|
inUse ? 'cursor-not-allowed' : 'cursor-pointer hover:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
||||||
onClick={() => handleModelClick(model.id)}
|
onClick={() => handleModelClick(model.id)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-2 md:gap-3">
|
||||||
<div className="w-8 h-8 flex items-center justify-center">
|
<div className="w-7 h-7 md:w-8 md:h-8 flex items-center justify-center flex-shrink-0">
|
||||||
{getModelIcon(model.provider || model.id, { width: 32, height: 32 }) || (
|
{getModelIcon(model.provider || model.id, { width: 28, height: 28 }) || (
|
||||||
<div className="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold"
|
<div className="w-7 h-7 md:w-8 md:h-8 rounded-full flex items-center justify-center text-xs md:text-sm font-bold"
|
||||||
style={{
|
style={{
|
||||||
background: model.id === 'deepseek' ? '#60a5fa' : '#c084fc',
|
background: model.id === 'deepseek' ? '#60a5fa' : '#c084fc',
|
||||||
color: '#fff'
|
color: '#fff'
|
||||||
}}>
|
}}>
|
||||||
@@ -570,63 +570,63 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0">
|
||||||
<div className="font-semibold" style={{ color: '#EAECEF' }}>{getShortName(model.name)}</div>
|
<div className="font-semibold text-sm md:text-base truncate" style={{ color: '#EAECEF' }}>{getShortName(model.name)}</div>
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||||
{inUse ? t('inUse', language) : model.enabled ? t('enabled', language) : t('configured', language)}
|
{inUse ? t('inUse', language) : model.enabled ? t('enabled', language) : t('configured', language)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`w-3 h-3 rounded-full ${model.enabled && model.apiKey ? 'bg-green-400' : 'bg-gray-500'}`} />
|
<div className={`w-2.5 h-2.5 md:w-3 md:h-3 rounded-full flex-shrink-0 ${model.enabled && model.apiKey ? 'bg-green-400' : 'bg-gray-500'}`} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{configuredModels.length === 0 && (
|
{configuredModels.length === 0 && (
|
||||||
<div className="text-center py-8" style={{ color: '#848E9C' }}>
|
<div className="text-center py-6 md:py-8" style={{ color: '#848E9C' }}>
|
||||||
<Brain className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
<Brain className="w-10 h-10 md:w-12 md:h-12 mx-auto mb-2 opacity-50" />
|
||||||
<div className="text-sm">{t('noModelsConfigured', language)}</div>
|
<div className="text-xs md:text-sm">{t('noModelsConfigured', language)}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Exchanges */}
|
{/* Exchanges */}
|
||||||
<div className="binance-card p-4">
|
<div className="binance-card p-3 md:p-4">
|
||||||
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
<h3 className="text-base md:text-lg font-semibold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||||
<Landmark className="w-5 h-5" style={{ color: '#F0B90B' }} />
|
<Landmark className="w-4 h-4 md:w-5 md:h-5" style={{ color: '#F0B90B' }} />
|
||||||
{t('exchanges', language)}
|
{t('exchanges', language)}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-2 md:space-y-3">
|
||||||
{configuredExchanges.map(exchange => {
|
{configuredExchanges.map(exchange => {
|
||||||
const inUse = isExchangeInUse(exchange.id);
|
const inUse = isExchangeInUse(exchange.id);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={exchange.id}
|
key={exchange.id}
|
||||||
className={`flex items-center justify-between p-3 rounded transition-all ${
|
className={`flex items-center justify-between p-2 md:p-3 rounded transition-all ${
|
||||||
inUse ? 'cursor-not-allowed' : 'cursor-pointer hover:bg-gray-700'
|
inUse ? 'cursor-not-allowed' : 'cursor-pointer hover:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
||||||
onClick={() => handleExchangeClick(exchange.id)}
|
onClick={() => handleExchangeClick(exchange.id)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-2 md:gap-3">
|
||||||
<div className="w-8 h-8 flex items-center justify-center">
|
<div className="w-7 h-7 md:w-8 md:h-8 flex items-center justify-center flex-shrink-0">
|
||||||
{getExchangeIcon(exchange.id, { width: 32, height: 32 })}
|
{getExchangeIcon(exchange.id, { width: 28, height: 28 })}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0">
|
||||||
<div className="font-semibold" style={{ color: '#EAECEF' }}>{getShortName(exchange.name)}</div>
|
<div className="font-semibold text-sm md:text-base truncate" style={{ color: '#EAECEF' }}>{getShortName(exchange.name)}</div>
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||||
{exchange.type.toUpperCase()} • {inUse ? t('inUse', language) : exchange.enabled ? t('enabled', language) : t('configured', language)}
|
{exchange.type.toUpperCase()} • {inUse ? t('inUse', language) : exchange.enabled ? t('enabled', language) : t('configured', language)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`w-3 h-3 rounded-full ${exchange.enabled && exchange.apiKey ? 'bg-green-400' : 'bg-gray-500'}`} />
|
<div className={`w-2.5 h-2.5 md:w-3 md:h-3 rounded-full flex-shrink-0 ${exchange.enabled && exchange.apiKey ? 'bg-green-400' : 'bg-gray-500'}`} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{configuredExchanges.length === 0 && (
|
{configuredExchanges.length === 0 && (
|
||||||
<div className="text-center py-8" style={{ color: '#848E9C' }}>
|
<div className="text-center py-6 md:py-8" style={{ color: '#848E9C' }}>
|
||||||
<Landmark className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
<Landmark className="w-10 h-10 md:w-12 md:h-12 mx-auto mb-2 opacity-50" />
|
||||||
<div className="text-sm">{t('noExchangesConfigured', language)}</div>
|
<div className="text-xs md:text-sm">{t('noExchangesConfigured', language)}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -634,47 +634,47 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Traders List */}
|
{/* Traders List */}
|
||||||
<div className="binance-card p-6">
|
<div className="binance-card p-4 md:p-6">
|
||||||
<div className="flex items-center justify-between mb-5">
|
<div className="flex items-center justify-between mb-4 md:mb-5">
|
||||||
<h2 className="text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
<h2 className="text-lg md:text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||||
<Users className="w-6 h-6" style={{ color: '#F0B90B' }} />
|
<Users className="w-5 h-5 md:w-6 md:h-6" style={{ color: '#F0B90B' }} />
|
||||||
{t('currentTraders', language)}
|
{t('currentTraders', language)}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{traders && traders.length > 0 ? (
|
{traders && traders.length > 0 ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-3 md:space-y-4">
|
||||||
{traders.map(trader => (
|
{traders.map(trader => (
|
||||||
<div key={trader.trader_id}
|
<div key={trader.trader_id}
|
||||||
className="flex items-center justify-between p-4 rounded transition-all hover:translate-y-[-1px]"
|
className="flex flex-col md:flex-row md:items-center justify-between p-3 md:p-4 rounded transition-all hover:translate-y-[-1px] gap-3 md:gap-4"
|
||||||
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}>
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-3 md:gap-4">
|
||||||
<div className="w-12 h-12 rounded-full flex items-center justify-center"
|
<div className="w-10 h-10 md:w-12 md:h-12 rounded-full flex items-center justify-center flex-shrink-0"
|
||||||
style={{
|
style={{
|
||||||
background: trader.ai_model.includes('deepseek') ? '#60a5fa' : '#c084fc',
|
background: trader.ai_model.includes('deepseek') ? '#60a5fa' : '#c084fc',
|
||||||
color: '#fff'
|
color: '#fff'
|
||||||
}}>
|
}}>
|
||||||
<Bot className="w-6 h-6" />
|
<Bot className="w-5 h-5 md:w-6 md:h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0">
|
||||||
<div className="font-bold text-lg" style={{ color: '#EAECEF' }}>
|
<div className="font-bold text-base md:text-lg truncate" style={{ color: '#EAECEF' }}>
|
||||||
{trader.trader_name}
|
{trader.trader_name}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm" style={{
|
<div className="text-xs md:text-sm truncate" style={{
|
||||||
color: trader.ai_model.includes('deepseek') ? '#60a5fa' : '#c084fc'
|
color: trader.ai_model.includes('deepseek') ? '#60a5fa' : '#c084fc'
|
||||||
}}>
|
}}>
|
||||||
{getModelDisplayName(trader.ai_model.split('_').pop() || trader.ai_model)} Model • {trader.exchange_id?.toUpperCase()}
|
{getModelDisplayName(trader.ai_model.split('_').pop() || trader.ai_model)} Model • {trader.exchange_id?.toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-3 md:gap-4 flex-wrap md:flex-nowrap">
|
||||||
{/* Status */}
|
{/* Status */}
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>{t('status', language)}</div>
|
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>{t('status', language)}</div>
|
||||||
<div className={`px-3 py-1 rounded text-xs font-bold ${
|
<div className={`px-2 md:px-3 py-1 rounded text-xs font-bold ${
|
||||||
trader.is_running ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
trader.is_running ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||||
}`} style={trader.is_running
|
}`} style={trader.is_running
|
||||||
? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
|
? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
|
||||||
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
||||||
}>
|
}>
|
||||||
@@ -683,20 +683,20 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-1.5 md:gap-2 flex-wrap md:flex-nowrap">
|
||||||
<button
|
<button
|
||||||
onClick={() => onTraderSelect?.(trader.trader_id)}
|
onClick={() => onTraderSelect?.(trader.trader_id)}
|
||||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1"
|
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1 whitespace-nowrap"
|
||||||
style={{ background: 'rgba(99, 102, 241, 0.1)', color: '#6366F1' }}
|
style={{ background: 'rgba(99, 102, 241, 0.1)', color: '#6366F1' }}
|
||||||
>
|
>
|
||||||
<BarChart3 className="w-4 h-4" />
|
<BarChart3 className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
{t('view', language)}
|
{t('view', language)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEditTrader(trader.trader_id)}
|
onClick={() => handleEditTrader(trader.trader_id)}
|
||||||
disabled={trader.is_running}
|
disabled={trader.is_running}
|
||||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
|
||||||
style={{
|
style={{
|
||||||
background: trader.is_running ? 'rgba(132, 142, 156, 0.1)' : 'rgba(255, 193, 7, 0.1)',
|
background: trader.is_running ? 'rgba(132, 142, 156, 0.1)' : 'rgba(255, 193, 7, 0.1)',
|
||||||
color: trader.is_running ? '#848E9C' : '#FFC107'
|
color: trader.is_running ? '#848E9C' : '#FFC107'
|
||||||
@@ -704,10 +704,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
>
|
>
|
||||||
✏️ {t('edit', language)}
|
✏️ {t('edit', language)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handleToggleTrader(trader.trader_id, trader.is_running || false)}
|
onClick={() => handleToggleTrader(trader.trader_id, trader.is_running || false)}
|
||||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 whitespace-nowrap"
|
||||||
style={trader.is_running
|
style={trader.is_running
|
||||||
? { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
? { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
||||||
: { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
|
: { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
|
||||||
@@ -718,10 +718,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteTrader(trader.trader_id)}
|
onClick={() => handleDeleteTrader(trader.trader_id)}
|
||||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105"
|
||||||
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}
|
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -729,15 +729,15 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-16" style={{ color: '#848E9C' }}>
|
<div className="text-center py-12 md:py-16" style={{ color: '#848E9C' }}>
|
||||||
<Bot className="w-24 h-24 mx-auto mb-4 opacity-50" />
|
<Bot className="w-16 h-16 md:w-24 md:h-24 mx-auto mb-3 md:mb-4 opacity-50" />
|
||||||
<div className="text-lg font-semibold mb-2">{t('noTraders', language)}</div>
|
<div className="text-base md:text-lg font-semibold mb-2">{t('noTraders', language)}</div>
|
||||||
<div className="text-sm mb-4">{t('createFirstTrader', language)}</div>
|
<div className="text-xs md:text-sm mb-3 md:mb-4">{t('createFirstTrader', language)}</div>
|
||||||
{(configuredModels.length === 0 || configuredExchanges.length === 0) && (
|
{(configuredModels.length === 0 || configuredExchanges.length === 0) && (
|
||||||
<div className="text-sm text-yellow-500">
|
<div className="text-xs md:text-sm text-yellow-500">
|
||||||
{configuredModels.length === 0 && configuredExchanges.length === 0
|
{configuredModels.length === 0 && configuredExchanges.length === 0
|
||||||
? t('configureModelsAndExchangesFirst', language)
|
? t('configureModelsAndExchangesFirst', language)
|
||||||
: configuredModels.length === 0
|
: configuredModels.length === 0
|
||||||
? t('configureModelsFirst', language)
|
? t('configureModelsFirst', language)
|
||||||
: t('configureExchangesFirst', language)
|
: t('configureExchangesFirst', language)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,24 +313,24 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<div className="mt-6 grid grid-cols-4 gap-4 pt-5" style={{ borderTop: '1px solid #2B3139' }}>
|
<div className="mt-6 grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4 pt-5" style={{ borderTop: '1px solid #2B3139' }}>
|
||||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
<div className="p-2 md:p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('comparisonMode', language)}</div>
|
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('comparisonMode', language)}</div>
|
||||||
<div className="text-base font-bold" style={{ color: '#EAECEF' }}>PnL %</div>
|
<div className="text-sm md:text-base font-bold" style={{ color: '#EAECEF' }}>PnL %</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
<div className="p-2 md:p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('dataPoints', language)}</div>
|
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('dataPoints', language)}</div>
|
||||||
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>{t('count', language, {count: combinedData.length})}</div>
|
<div className="text-sm md:text-base font-bold mono" style={{ color: '#EAECEF' }}>{t('count', language, {count: combinedData.length})}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
<div className="p-2 md:p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('currentGap', language)}</div>
|
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('currentGap', language)}</div>
|
||||||
<div className="text-base font-bold mono" style={{ color: currentGap > 1 ? '#F0B90B' : '#EAECEF' }}>
|
<div className="text-sm md:text-base font-bold mono" style={{ color: currentGap > 1 ? '#F0B90B' : '#EAECEF' }}>
|
||||||
{currentGap.toFixed(2)}%
|
{currentGap.toFixed(2)}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
<div className="p-2 md:p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('displayRange', language)}</div>
|
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('displayRange', language)}</div>
|
||||||
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>
|
<div className="text-sm md:text-base font-bold mono" style={{ color: '#EAECEF' }}>
|
||||||
{combinedData.length > MAX_DISPLAY_POINTS
|
{combinedData.length > MAX_DISPLAY_POINTS
|
||||||
? `${t('recent', language)} ${MAX_DISPLAY_POINTS}`
|
? `${t('recent', language)} ${MAX_DISPLAY_POINTS}`
|
||||||
: t('allData', language)}
|
: t('allData', language)}
|
||||||
|
|||||||
@@ -75,13 +75,16 @@ export function CompetitionPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-5 animate-fade-in">
|
<div className="space-y-5 animate-fade-in">
|
||||||
{/* Competition Header - 精简版 */}
|
{/* Competition Header - 精简版 */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 md:gap-0">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-3 md:gap-4">
|
||||||
<div className="w-12 h-12 rounded-xl flex items-center justify-center" style={{ background: 'rgba(240, 185, 11, 0.15)', border: '1px solid rgba(240,185,11,0.3)' }}>
|
<div className="w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center" style={{
|
||||||
<Trophy className="w-6 h-6" style={{ color: '#F0B90B' }} />
|
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
|
||||||
|
boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)'
|
||||||
|
}}>
|
||||||
|
<Trophy className="w-6 h-6 md:w-7 md:h-7" style={{ color: '#000' }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
<h1 className="text-xl md:text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||||
{t('aiCompetition', language)}
|
{t('aiCompetition', language)}
|
||||||
<span className="text-xs font-normal px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.15)', color: '#F0B90B' }}>
|
<span className="text-xs font-normal px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.15)', color: '#F0B90B' }}>
|
||||||
{competition.count} {t('traders', language)}
|
{competition.count} {t('traders', language)}
|
||||||
@@ -92,9 +95,9 @@ export function CompetitionPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-left md:text-right w-full md:w-auto">
|
||||||
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>{t('leader', language)}</div>
|
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>{t('leader', language)}</div>
|
||||||
<div className="text-lg font-bold" style={{ color: '#F0B90B' }}>{leader?.trader_name}</div>
|
<div className="text-base md:text-lg font-bold" style={{ color: '#F0B90B' }}>{leader?.trader_name}</div>
|
||||||
<div className="text-sm font-semibold" style={{ color: (leader?.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}>
|
<div className="text-sm font-semibold" style={{ color: (leader?.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}>
|
||||||
{(leader?.total_pnl ?? 0) >= 0 ? '+' : ''}{leader?.total_pnl_pct?.toFixed(2) || '0.00'}%
|
{(leader?.total_pnl ?? 0) >= 0 ? '+' : ''}{leader?.total_pnl_pct?.toFixed(2) || '0.00'}%
|
||||||
</div>
|
</div>
|
||||||
@@ -157,20 +160,20 @@ export function CompetitionPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-2 md:gap-3 flex-wrap md:flex-nowrap">
|
||||||
{/* Total Equity */}
|
{/* Total Equity */}
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>{t('equity', language)}</div>
|
<div className="text-xs" style={{ color: '#848E9C' }}>{t('equity', language)}</div>
|
||||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
<div className="text-xs md:text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||||
{trader.total_equity?.toFixed(2) || '0.00'}
|
{trader.total_equity?.toFixed(2) || '0.00'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* P&L */}
|
{/* P&L */}
|
||||||
<div className="text-right min-w-[90px]">
|
<div className="text-right min-w-[70px] md:min-w-[90px]">
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>{t('pnl', language)}</div>
|
<div className="text-xs" style={{ color: '#848E9C' }}>{t('pnl', language)}</div>
|
||||||
<div
|
<div
|
||||||
className="text-lg font-bold mono"
|
className="text-base md:text-lg font-bold mono"
|
||||||
style={{ color: (trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}
|
style={{ color: (trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}
|
||||||
>
|
>
|
||||||
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}
|
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}
|
||||||
@@ -184,7 +187,7 @@ export function CompetitionPage() {
|
|||||||
{/* Positions */}
|
{/* Positions */}
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>{t('pos', language)}</div>
|
<div className="text-xs" style={{ color: '#848E9C' }}>{t('pos', language)}</div>
|
||||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
<div className="text-xs md:text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||||
{trader.position_count}
|
{trader.position_count}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||||
@@ -244,15 +247,12 @@ export function CompetitionPage() {
|
|||||||
>
|
>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div
|
<div
|
||||||
className="text-base font-bold mb-1"
|
className="text-sm md:text-base font-bold mb-2"
|
||||||
style={{ color: getTraderColor(sortedTraders, trader.trader_id) }}
|
style={{ color: getTraderColor(sortedTraders, trader.trader_id) }}
|
||||||
>
|
>
|
||||||
{trader.trader_name}
|
{trader.trader_name}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs mono mb-2" style={{ color: '#848E9C' }}>
|
<div className="text-lg md:text-2xl font-bold mono mb-1" style={{ color: (trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}>
|
||||||
{trader.ai_model.toUpperCase()} + {trader.exchange.toUpperCase()}
|
|
||||||
</div>
|
|
||||||
<div className="text-2xl font-bold mono mb-1" style={{ color: (trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}>
|
|
||||||
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}{trader.total_pnl_pct?.toFixed(2) || '0.00'}%
|
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}{trader.total_pnl_pct?.toFixed(2) || '0.00'}%
|
||||||
</div>
|
</div>
|
||||||
{isWinning && gap > 0 && (
|
{isWinning && gap > 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user