import { useState } from 'react' import { Plus, X, Database, TrendingUp, TrendingDown, List, Ban, Zap } from 'lucide-react' import type { CoinSourceConfig } from '../../types' import { coinSource, ts } from '../../i18n/strategy-translations' import { NofxSelect } from '../ui/select' 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 sourceTypes = [ { value: 'static', icon: List, color: '#848E9C' }, { value: 'ai500', icon: Database, color: '#F0B90B' }, { value: 'oi_top', icon: TrendingUp, color: '#0ECB81' }, { value: 'oi_low', icon: TrendingDown, color: '#F6465D' }, ] 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 MAX_STATIC_COINS = 10 const showToast = (msg: string) => { const toast = document.createElement('div') toast.textContent = msg toast.className = 'fixed top-4 left-1/2 -translate-x-1/2 px-4 py-2 rounded-lg text-sm z-50 shadow-lg' toast.style.cssText = 'background:#F6465D;color:#fff;' document.body.appendChild(toast) setTimeout(() => toast.remove(), 2000) } const handleAddCoin = () => { if (!newCoin.trim()) return const currentCoins = config.static_coins || [] if (currentCoins.length >= MAX_STATIC_COINS) { showToast(language === 'zh' ? `最多添加 ${MAX_STATIC_COINS} 个币种` : `Maximum ${MAX_STATIC_COINS} coins allowed`) 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` } 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 - only for static mode */} {config.source_type === 'static' && (
{(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 */}

{ts(coinSource.excludedCoinsDesc, language)}

{(config.excluded_coins || []).map((coin) => ( {coin} {!disabled && ( )} ))} {(config.excluded_coins || []).length === 0 && ( {ts(coinSource.excludedNone, language)} )}
{!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 - only for ai500 mode */} {config.source_type === 'ai500' && (
AI500 {ts(coinSource.dataSourceConfig, language)}
{config.use_ai500 && (
{ts(coinSource.ai500Limit, language)}: !disabled && onChange({ ...config, ai500_limit: parseInt(val) || 3 }) } disabled={disabled} options={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(n => ({ value: n, label: String(n) }))} className="px-3 py-1.5 rounded bg-nofx-bg border border-nofx-gold/20 text-nofx-text" />
)}

{ts(coinSource.nofxosNote, language)}

)} {/* OI Top Options - only for oi_top mode */} {config.source_type === 'oi_top' && (
{ts(coinSource.oiIncreaseTitle, language)} {ts(coinSource.dataSourceConfig, language)}
{config.use_oi_top && (
{ts(coinSource.oiTopLimit, language)}: !disabled && onChange({ ...config, oi_top_limit: parseInt(val) || 3 }) } disabled={disabled} options={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(n => ({ value: n, label: String(n) }))} className="px-3 py-1.5 rounded bg-nofx-bg border border-nofx-gold/20 text-nofx-text" />
)}

{ts(coinSource.nofxosNote, language)}

)} {/* OI Low Options - only for oi_low mode */} {config.source_type === 'oi_low' && (
{ts(coinSource.oiDecreaseTitle, language)} {ts(coinSource.dataSourceConfig, language)}
{config.use_oi_low && (
{ts(coinSource.oiLowLimit, language)}: !disabled && onChange({ ...config, oi_low_limit: parseInt(val) || 3 }) } disabled={disabled} options={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(n => ({ value: n, label: String(n) }))} className="px-3 py-1.5 rounded bg-nofx-bg border border-nofx-gold/20 text-nofx-text" />
)}

{ts(coinSource.nofxosNote, language)}

)}
) }