import { useState, useEffect } from 'react' import { toast } from 'sonner' import { User, Cpu, Building2, MessageCircle, Eye, EyeOff, ChevronRight, Plus, Pencil } from 'lucide-react' import { useAuth } from '../contexts/AuthContext' import { useLanguage } from '../contexts/LanguageContext' import { api } from '../lib/api' import { getPostAuthPath, getUserMode, setUserMode, type UserMode, } from '../lib/onboarding' import { ExchangeConfigModal } from '../components/trader/ExchangeConfigModal' import { TelegramConfigModal } from '../components/trader/TelegramConfigModal' import { ModelConfigModal } from '../components/trader/ModelConfigModal' import type { Exchange, AIModel } from '../types' type Tab = 'account' | 'models' | 'exchanges' | 'telegram' export function SettingsPage() { const { user } = useAuth() const { language } = useLanguage() const [activeTab, setActiveTab] = useState('account') const [userMode, setUserModeState] = useState(() => getUserMode() ?? 'advanced') // Account state const [newPassword, setNewPassword] = useState('') const [showPassword, setShowPassword] = useState(false) const [changingPassword, setChangingPassword] = useState(false) // AI Models state const [configuredModels, setConfiguredModels] = useState([]) const [supportedModels, setSupportedModels] = useState([]) const [showModelModal, setShowModelModal] = useState(false) const [editingModel, setEditingModel] = useState(null) // Exchanges state const [exchanges, setExchanges] = useState([]) const [showExchangeModal, setShowExchangeModal] = useState(false) const [editingExchange, setEditingExchange] = useState(null) // Telegram state const [showTelegramModal, setShowTelegramModal] = useState(false) // Fetch data when tabs are visited useEffect(() => { if (activeTab === 'models') { Promise.all([api.getModelConfigs(), api.getSupportedModels()]) .then(([configs, supported]) => { setConfiguredModels(configs) setSupportedModels(supported) }) .catch(() => toast.error('Failed to load AI models')) } if (activeTab === 'exchanges') { api.getExchangeConfigs() .then(setExchanges) .catch(() => toast.error('Failed to load exchanges')) } }, [activeTab]) const handleChangePassword = async (e: React.FormEvent) => { e.preventDefault() if (newPassword.length < 8) { toast.error('Password must be at least 8 characters') return } setChangingPassword(true) try { const res = await fetch('/api/user/password', { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem('token') || ''}`, }, body: JSON.stringify({ new_password: newPassword }), }) if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data.error || 'Failed to update password') } toast.success('Password updated successfully') setNewPassword('') } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to update password') } finally { setChangingPassword(false) } } const handleSwitchMode = (nextMode: UserMode) => { if (nextMode === userMode) { return } setUserMode(nextMode) setUserModeState(nextMode) toast.success( language === 'zh' ? `已切换到${nextMode === 'beginner' ? '新手模式' : '老手模式'}` : nextMode === 'beginner' ? 'Switched to beginner mode' : 'Switched to advanced mode' ) const nextPath = getPostAuthPath(nextMode) window.history.pushState({}, '', nextPath) window.dispatchEvent(new PopStateEvent('popstate')) } const handleSaveModel = async ( modelId: string, apiKey: string, customApiUrl?: string, customModelName?: string ) => { try { const existingModel = configuredModels.find((m) => m.id === modelId) const modelTemplate = supportedModels.find((m) => m.id === modelId) const modelToUpdate = existingModel || modelTemplate if (!modelToUpdate) { toast.error('Model not found'); return } let updatedModels: AIModel[] if (existingModel) { updatedModels = configuredModels.map((m) => m.id === modelId ? { ...m, apiKey, customApiUrl: customApiUrl || '', customModelName: customModelName || '', enabled: true } : m ) } else { updatedModels = [...configuredModels, { ...modelToUpdate, apiKey, customApiUrl: customApiUrl || '', customModelName: customModelName || '', enabled: true, }] } const request = { models: Object.fromEntries( updatedModels.map((m) => [m.provider, { enabled: m.enabled, api_key: m.apiKey || '', custom_api_url: m.customApiUrl || '', custom_model_name: m.customModelName || '', }]) ), } await api.updateModelConfigs(request) toast.success('Model config saved') const refreshed = await api.getModelConfigs() setConfiguredModels(refreshed) setShowModelModal(false) setEditingModel(null) } catch { toast.error('Failed to save model config') } } const handleDeleteModel = async (modelId: string) => { try { const updatedModels = configuredModels.map((m) => m.id === modelId ? { ...m, apiKey: '', customApiUrl: '', customModelName: '', enabled: false } : m ) const request = { models: Object.fromEntries( updatedModels.map((m) => [m.provider, { enabled: m.enabled, api_key: m.apiKey || '', custom_api_url: m.customApiUrl || '', custom_model_name: m.customModelName || '', }]) ), } await api.updateModelConfigs(request) const refreshed = await api.getModelConfigs() setConfiguredModels(refreshed) setShowModelModal(false) setEditingModel(null) toast.success('Model config removed') } catch { toast.error('Failed to remove model config') } } const handleSaveExchange = async ( exchangeId: string | null, exchangeType: string, accountName: string, apiKey: string, secretKey?: string, passphrase?: string, testnet?: boolean, hyperliquidWalletAddr?: string, asterUser?: string, asterSigner?: string, asterPrivateKey?: string, lighterWalletAddr?: string, lighterPrivateKey?: string, lighterApiKeyPrivateKey?: string, lighterApiKeyIndex?: number ) => { try { if (exchangeId) { const request = { exchanges: { [exchangeId]: { enabled: true, api_key: apiKey || '', secret_key: secretKey || '', passphrase: passphrase || '', testnet: testnet || false, hyperliquid_wallet_addr: hyperliquidWalletAddr || '', aster_user: asterUser || '', aster_signer: asterSigner || '', aster_private_key: asterPrivateKey || '', lighter_wallet_addr: lighterWalletAddr || '', lighter_private_key: lighterPrivateKey || '', lighter_api_key_private_key: lighterApiKeyPrivateKey || '', lighter_api_key_index: lighterApiKeyIndex || 0, }, }, } await api.updateExchangeConfigsEncrypted(request) toast.success('Exchange config updated') } else { const createRequest = { exchange_type: exchangeType, account_name: accountName, enabled: true, api_key: apiKey || '', secret_key: secretKey || '', passphrase: passphrase || '', testnet: testnet || false, hyperliquid_wallet_addr: hyperliquidWalletAddr || '', aster_user: asterUser || '', aster_signer: asterSigner || '', aster_private_key: asterPrivateKey || '', lighter_wallet_addr: lighterWalletAddr || '', lighter_private_key: lighterPrivateKey || '', lighter_api_key_private_key: lighterApiKeyPrivateKey || '', lighter_api_key_index: lighterApiKeyIndex || 0, } await api.createExchangeEncrypted(createRequest) toast.success('Exchange account created') } const refreshed = await api.getExchangeConfigs() setExchanges(refreshed) setShowExchangeModal(false) setEditingExchange(null) } catch { toast.error('Failed to save exchange config') } } const handleDeleteExchange = async (exchangeId: string) => { try { await api.deleteExchange(exchangeId) toast.success('Exchange account deleted') const refreshed = await api.getExchangeConfigs() setExchanges(refreshed) setShowExchangeModal(false) setEditingExchange(null) } catch { toast.error('Failed to delete exchange account') } } const tabs: { key: Tab; label: string; icon: React.ReactNode }[] = [ { key: 'account', label: 'Account', icon: }, { key: 'models', label: 'AI Models', icon: }, { key: 'exchanges', label: 'Exchanges', icon: }, { key: 'telegram', label: 'Telegram', icon: }, ] return (

Settings

{/* Tabs */}
{tabs.map((tab) => ( ))}
{/* Tab Content */}
{/* Account Tab */} {activeTab === 'account' && (

Email

{user?.email}

{language === 'zh' ? '使用模式' : 'Usage Mode'}

{language === 'zh' ? '新手模式会显示钱包引导和 4 步卡片;老手模式保持原来的专业界面。' : 'Beginner mode shows wallet onboarding and quickstart cards. Advanced mode keeps the original pro workflow.'}

{userMode === 'beginner' ? language === 'zh' ? '当前:新手模式' : 'Current: Beginner' : language === 'zh' ? '当前:老手模式' : 'Current: Advanced'}

Change Password

setNewPassword(e.target.value)} className="w-full bg-zinc-950/80 border border-zinc-700/80 rounded-xl px-4 py-3 pr-11 text-sm text-white placeholder-zinc-600 focus:outline-none focus:border-nofx-gold/60 focus:ring-1 focus:ring-nofx-gold/30 transition-all" placeholder="At least 8 characters" required />
)} {/* AI Models Tab */} {activeTab === 'models' && (

{configuredModels.length} model{configuredModels.length !== 1 ? 's' : ''} configured

{configuredModels.length === 0 ? (
No AI models configured yet
) : (
{configuredModels.map((model) => ( ))}
)}
)} {/* Exchanges Tab */} {activeTab === 'exchanges' && (

{exchanges.length} account{exchanges.length !== 1 ? 's' : ''} connected

{exchanges.length === 0 ? (
No exchange accounts connected yet
) : (
{exchanges.map((exchange) => ( ))}
)}
)} {/* Telegram Tab */} {activeTab === 'telegram' && (

Connect a Telegram bot to receive trading notifications and interact with your traders.

)}
{/* AI Model Modal */} {showModelModal && (
{ setShowModelModal(false); setEditingModel(null) }} language={language} />
)} {/* Exchange Modal */} {showExchangeModal && (
{ setShowExchangeModal(false); setEditingExchange(null) }} language={language} />
)} {/* Telegram Modal */} {showTelegramModal && (
setShowTelegramModal(false)} language={language} />
)}
) }