import { useState } from 'react' import { Plus, X, Database, TrendingUp, List, Ban, Zap } from 'lucide-react' import type { CoinSourceConfig } from '../../types' interface CoinSourceEditorProps { config: CoinSourceConfig onChange: (config: CoinSourceConfig) => void disabled?: boolean language: string } export function CoinSourceEditor({ config, onChange, disabled, language, }: CoinSourceEditorProps) { const [newCoin, setNewCoin] = useState('') const [newExcludedCoin, setNewExcludedCoin] = useState('') const t = (key: string) => { const translations: Record> = { sourceType: { zh: '数据来源类型', en: 'Source Type' }, static: { zh: '静态列表', en: 'Static List' }, ai500: { zh: 'AI500 数据源', en: 'AI500 Data Provider' }, oi_top: { zh: 'OI Top 持仓增长', en: 'OI Top' }, mixed: { zh: '混合模式', en: 'Mixed Mode' }, staticCoins: { zh: '自定义币种', en: 'Custom Coins' }, addCoin: { zh: '添加币种', en: 'Add Coin' }, useAI500: { zh: '启用 AI500 数据源', en: 'Enable AI500 Data Provider' }, ai500Limit: { zh: '数量上限', en: 'Limit' }, useOITop: { zh: '启用 OI Top 数据', en: 'Enable OI Top' }, oiTopLimit: { zh: '数量上限', en: 'Limit' }, staticDesc: { zh: '手动指定交易币种列表', en: 'Manually specify trading coins' }, ai500Desc: { zh: '使用 AI500 智能筛选的热门币种', en: 'Use AI500 smart-filtered popular coins', }, oiTopDesc: { zh: '使用持仓量增长最快的币种', en: 'Use coins with fastest OI growth', }, mixedDesc: { zh: '组合多种数据源,AI500 + OI Top + 自定义', en: 'Combine multiple sources: AI500 + OI Top + Custom', }, dataSourceConfig: { zh: '数据源配置', en: 'Data Source Configuration' }, excludedCoins: { zh: '排除币种', en: 'Excluded Coins' }, excludedCoinsDesc: { zh: '这些币种将从所有数据源中排除,不会被交易', en: 'These coins will be excluded from all sources and will not be traded' }, addExcludedCoin: { zh: '添加排除', en: 'Add Excluded' }, nofxosNote: { zh: '使用 NofxOS API Key(在指标配置中设置)', en: 'Uses NofxOS API Key (set in Indicators config)' }, } return translations[key]?.[language] || key } const sourceTypes = [ { value: 'static', icon: List, color: '#848E9C' }, { value: 'ai500', icon: Database, color: '#F0B90B' }, { value: 'oi_top', icon: TrendingUp, color: '#0ECB81' }, { value: 'mixed', icon: Database, color: '#60a5fa' }, ] as const // xyz dex assets (stocks, forex, commodities) - should NOT get USDT suffix const xyzDexAssets = new Set([ // Stocks 'TSLA', 'NVDA', 'AAPL', 'MSFT', 'META', 'AMZN', 'GOOGL', 'AMD', 'COIN', 'NFLX', 'PLTR', 'HOOD', 'INTC', 'MSTR', 'TSM', 'ORCL', 'MU', 'RIVN', 'COST', 'LLY', 'CRCL', 'SKHX', 'SNDK', // Forex 'EUR', 'JPY', // Commodities 'GOLD', 'SILVER', // Index 'XYZ100', ]) const isXyzDexAsset = (symbol: string): boolean => { const base = symbol.toUpperCase().replace(/^XYZ:/, '').replace(/USDT$|USD$|-USDC$/, '') return xyzDexAssets.has(base) } const handleAddCoin = () => { if (!newCoin.trim()) return const symbol = newCoin.toUpperCase().trim() // For xyz dex assets (stocks, forex, commodities), use xyz: prefix without USDT let formattedSymbol: string if (isXyzDexAsset(symbol)) { // Remove xyz: prefix (case-insensitive) and any USD suffixes const base = symbol.replace(/^xyz:/i, '').replace(/USDT$|USD$|-USDC$/i, '') formattedSymbol = `xyz:${base}` } else { formattedSymbol = symbol.endsWith('USDT') ? symbol : `${symbol}USDT` } const currentCoins = config.static_coins || [] if (!currentCoins.includes(formattedSymbol)) { onChange({ ...config, static_coins: [...currentCoins, formattedSymbol], }) } setNewCoin('') } const handleRemoveCoin = (coin: string) => { onChange({ ...config, static_coins: (config.static_coins || []).filter((c) => c !== coin), }) } const handleAddExcludedCoin = () => { if (!newExcludedCoin.trim()) return const symbol = newExcludedCoin.toUpperCase().trim() // For xyz dex assets, use xyz: prefix without USDT let formattedSymbol: string if (isXyzDexAsset(symbol)) { const base = symbol.replace(/^xyz:/i, '').replace(/USDT$|USD$|-USDC$/i, '') formattedSymbol = `xyz:${base}` } else { formattedSymbol = symbol.endsWith('USDT') ? symbol : `${symbol}USDT` } const currentExcluded = config.excluded_coins || [] if (!currentExcluded.includes(formattedSymbol)) { onChange({ ...config, excluded_coins: [...currentExcluded, formattedSymbol], }) } setNewExcludedCoin('') } const handleRemoveExcludedCoin = (coin: string) => { onChange({ ...config, excluded_coins: (config.excluded_coins || []).filter((c) => c !== coin), }) } // NofxOS badge component const NofxOSBadge = () => ( NofxOS ) return (
{/* Source Type Selector */}
{sourceTypes.map(({ value, icon: Icon, color }) => ( ))}
{/* Static Coins */} {(config.source_type === 'static' || config.source_type === 'mixed') && (
{(config.static_coins || []).map((coin) => ( {coin} {!disabled && ( )} ))}
{!disabled && (
setNewCoin(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleAddCoin()} placeholder="BTC, ETH, SOL..." className="flex-1 px-4 py-2 rounded-lg bg-nofx-bg border border-nofx-gold/20 text-nofx-text" />
)}
)} {/* Excluded Coins */}

{t('excludedCoinsDesc')}

{(config.excluded_coins || []).map((coin) => ( {coin} {!disabled && ( )} ))} {(config.excluded_coins || []).length === 0 && ( {language === 'zh' ? '无' : 'None'} )}
{!disabled && (
setNewExcludedCoin(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleAddExcludedCoin()} placeholder="BTC, ETH, DOGE..." className="flex-1 px-4 py-2 rounded-lg text-sm bg-nofx-bg border border-nofx-gold/20 text-nofx-text" />
)}
{/* AI500 Options */} {(config.source_type === 'ai500' || config.source_type === 'mixed') && (
AI500 {t('dataSourceConfig')}
{config.use_ai500 && (
{t('ai500Limit')}:
)}

{t('nofxosNote')}

)} {/* OI Top Options */} {(config.source_type === 'oi_top' || config.source_type === 'mixed') && (
OI Top {t('dataSourceConfig')}
{config.use_oi_top && (
{t('oiTopLimit')}:
)}

{t('nofxosNote')}

)}
) }