refactor: replace window.location with useNavigate for routing in auth components (#1470)

Co-authored-by: Dean <afei.wuhao@gmail.com>
This commit is contained in:
deanokk
2026-04-13 23:44:14 +08:00
committed by GitHub
parent eef78b7987
commit e1b5a5d833
22 changed files with 1930 additions and 1273 deletions

View File

@@ -1,6 +1,17 @@
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { toast } from 'sonner'
import { User, Cpu, Building2, MessageCircle, Eye, EyeOff, ChevronRight, Plus, Pencil } from 'lucide-react'
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'
@@ -20,8 +31,11 @@ type Tab = 'account' | 'models' | 'exchanges' | 'telegram'
export function SettingsPage() {
const { user } = useAuth()
const { language } = useLanguage()
const navigate = useNavigate()
const [activeTab, setActiveTab] = useState<Tab>('account')
const [userMode, setUserModeState] = useState<UserMode>(() => getUserMode() ?? 'advanced')
const [userMode, setUserModeState] = useState<UserMode>(
() => getUserMode() ?? 'advanced'
)
// Account state
const [newPassword, setNewPassword] = useState('')
@@ -53,7 +67,8 @@ export function SettingsPage() {
.catch(() => toast.error('Failed to load AI models'))
}
if (activeTab === 'exchanges') {
api.getExchangeConfigs()
api
.getExchangeConfigs()
.then(setExchanges)
.catch(() => toast.error('Failed to load exchanges'))
}
@@ -82,7 +97,9 @@ export function SettingsPage() {
toast.success('Password updated successfully')
setNewPassword('')
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Failed to update password')
toast.error(
err instanceof Error ? err.message : 'Failed to update password'
)
} finally {
setChangingPassword(false)
}
@@ -104,8 +121,7 @@ export function SettingsPage() {
)
const nextPath = getPostAuthPath(nextMode)
window.history.pushState({}, '', nextPath)
window.dispatchEvent(new PopStateEvent('popstate'))
navigate(nextPath)
}
const handleSaveModel = async (
@@ -118,33 +134,48 @@ export function SettingsPage() {
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 }
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,
apiKey,
customApiUrl: customApiUrl || '',
customModelName: customModelName || '',
enabled: true,
}
: m
)
} else {
updatedModels = [...configuredModels, {
...modelToUpdate,
apiKey,
customApiUrl: customApiUrl || '',
customModelName: customModelName || '',
enabled: true,
}]
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 || '',
}])
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)
@@ -161,16 +192,27 @@ export function SettingsPage() {
const handleDeleteModel = async (modelId: string) => {
try {
const updatedModels = configuredModels.map((m) =>
m.id === modelId ? { ...m, apiKey: '', customApiUrl: '', customModelName: '', enabled: false } : 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 || '',
}])
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)
@@ -223,7 +265,7 @@ export function SettingsPage() {
},
}
await api.updateExchangeConfigsEncrypted(request)
toast.success('Exchange config updated')
toast.success('Exchange config updated')
} else {
const createRequest = {
exchange_type: exchangeType,
@@ -243,7 +285,7 @@ export function SettingsPage() {
lighter_api_key_index: lighterApiKeyIndex || 0,
}
await api.createExchangeEncrypted(createRequest)
toast.success('Exchange account created')
toast.success('Exchange account created')
}
const refreshed = await api.getExchangeConfigs()
setExchanges(refreshed)
@@ -275,7 +317,10 @@ export function SettingsPage() {
]
return (
<div className="min-h-screen pt-20 pb-12 px-4" style={{ background: '#0B0E11' }}>
<div
className="min-h-screen pt-20 pb-12 px-4"
style={{ background: '#0B0E11' }}
>
<div className="max-w-2xl mx-auto">
<h1 className="text-xl font-bold text-white mb-6">Settings</h1>
@@ -286,9 +331,10 @@ export function SettingsPage() {
key={tab.key}
onClick={() => setActiveTab(tab.key)}
className={`flex-1 flex items-center justify-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-all
${activeTab === tab.key
? 'bg-nofx-gold text-black'
: 'text-zinc-400 hover:text-white'
${
activeTab === tab.key
? 'bg-nofx-gold text-black'
: 'text-zinc-400 hover:text-white'
}`}
>
{tab.icon}
@@ -299,7 +345,6 @@ export function SettingsPage() {
{/* Tab Content */}
<div className="bg-zinc-900/60 backdrop-blur-xl border border-zinc-800/80 rounded-2xl p-6">
{/* Account Tab */}
{activeTab === 'account' && (
<div className="space-y-6">
@@ -322,8 +367,12 @@ export function SettingsPage() {
</div>
<span className="rounded-full border border-nofx-gold/20 bg-nofx-gold/10 px-3 py-1 text-xs font-semibold text-nofx-gold">
{userMode === 'beginner'
? language === 'zh' ? '当前:新手模式' : 'Current: Beginner'
: language === 'zh' ? '当前:手模式' : 'Current: Advanced'}
? language === 'zh'
? '当前:手模式'
: 'Current: Beginner'
: language === 'zh'
? '当前:老手模式'
: 'Current: Advanced'}
</span>
</div>
@@ -369,10 +418,14 @@ export function SettingsPage() {
</div>
<div className="border-t border-zinc-800 pt-6">
<h3 className="text-sm font-semibold text-white mb-4">Change Password</h3>
<h3 className="text-sm font-semibold text-white mb-4">
Change Password
</h3>
<form onSubmit={handleChangePassword} className="space-y-4">
<div>
<label className="block text-xs font-medium text-zinc-400 mb-2">New Password</label>
<label className="block text-xs font-medium text-zinc-400 mb-2">
New Password
</label>
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
@@ -387,7 +440,11 @@ export function SettingsPage() {
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300 transition-colors"
>
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
{showPassword ? (
<EyeOff size={16} />
) : (
<Eye size={16} />
)}
</button>
</div>
</div>
@@ -408,10 +465,14 @@ export function SettingsPage() {
<div className="space-y-4">
<div className="flex items-center justify-between">
<p className="text-sm text-zinc-400">
{configuredModels.length} model{configuredModels.length !== 1 ? 's' : ''} configured
{configuredModels.length} model
{configuredModels.length !== 1 ? 's' : ''} configured
</p>
<button
onClick={() => { setEditingModel(null); setShowModelModal(true) }}
onClick={() => {
setEditingModel(null)
setShowModelModal(true)
}}
className="flex items-center gap-1.5 text-xs font-medium bg-nofx-gold/10 hover:bg-nofx-gold/20 text-nofx-gold px-3 py-1.5 rounded-lg transition-colors"
>
<Plus size={14} />
@@ -428,7 +489,10 @@ export function SettingsPage() {
{configuredModels.map((model) => (
<button
key={model.id}
onClick={() => { setEditingModel(model.id); setShowModelModal(true) }}
onClick={() => {
setEditingModel(model.id)
setShowModelModal(true)
}}
className="w-full flex items-center justify-between px-4 py-3 rounded-xl bg-zinc-800/50 hover:bg-zinc-800 border border-zinc-700/50 transition-colors group"
>
<div className="flex items-center gap-3">
@@ -436,15 +500,24 @@ export function SettingsPage() {
<Cpu size={14} className="text-zinc-300" />
</div>
<div className="text-left">
<p className="text-sm font-medium text-white">{model.name}</p>
<p className="text-xs text-zinc-500">{model.provider}</p>
<p className="text-sm font-medium text-white">
{model.name}
</p>
<p className="text-xs text-zinc-500">
{model.provider}
</p>
</div>
</div>
<div className="flex items-center gap-2">
<span className={`text-xs px-2 py-0.5 rounded-full ${model.enabled ? 'bg-emerald-500/10 text-emerald-400' : 'bg-zinc-700 text-zinc-500'}`}>
<span
className={`text-xs px-2 py-0.5 rounded-full ${model.enabled ? 'bg-emerald-500/10 text-emerald-400' : 'bg-zinc-700 text-zinc-500'}`}
>
{model.enabled ? 'Active' : 'Inactive'}
</span>
<Pencil size={14} className="text-zinc-600 group-hover:text-zinc-400 transition-colors" />
<Pencil
size={14}
className="text-zinc-600 group-hover:text-zinc-400 transition-colors"
/>
</div>
</button>
))}
@@ -458,10 +531,14 @@ export function SettingsPage() {
<div className="space-y-4">
<div className="flex items-center justify-between">
<p className="text-sm text-zinc-400">
{exchanges.length} account{exchanges.length !== 1 ? 's' : ''} connected
{exchanges.length} account{exchanges.length !== 1 ? 's' : ''}{' '}
connected
</p>
<button
onClick={() => { setEditingExchange(null); setShowExchangeModal(true) }}
onClick={() => {
setEditingExchange(null)
setShowExchangeModal(true)
}}
className="flex items-center gap-1.5 text-xs font-medium bg-nofx-gold/10 hover:bg-nofx-gold/20 text-nofx-gold px-3 py-1.5 rounded-lg transition-colors"
>
<Plus size={14} />
@@ -478,7 +555,10 @@ export function SettingsPage() {
{exchanges.map((exchange) => (
<button
key={exchange.id}
onClick={() => { setEditingExchange(exchange.id); setShowExchangeModal(true) }}
onClick={() => {
setEditingExchange(exchange.id)
setShowExchangeModal(true)
}}
className="w-full flex items-center justify-between px-4 py-3 rounded-xl bg-zinc-800/50 hover:bg-zinc-800 border border-zinc-700/50 transition-colors group"
>
<div className="flex items-center gap-3">
@@ -486,11 +566,18 @@ export function SettingsPage() {
<Building2 size={14} className="text-zinc-300" />
</div>
<div className="text-left">
<p className="text-sm font-medium text-white">{exchange.account_name || exchange.name}</p>
<p className="text-xs text-zinc-500 capitalize">{exchange.exchange_type || exchange.type}</p>
<p className="text-sm font-medium text-white">
{exchange.account_name || exchange.name}
</p>
<p className="text-xs text-zinc-500 capitalize">
{exchange.exchange_type || exchange.type}
</p>
</div>
</div>
<ChevronRight size={14} className="text-zinc-600 group-hover:text-zinc-400 transition-colors" />
<ChevronRight
size={14}
className="text-zinc-600 group-hover:text-zinc-400 transition-colors"
/>
</button>
))}
</div>
@@ -502,7 +589,8 @@ export function SettingsPage() {
{activeTab === 'telegram' && (
<div className="space-y-4">
<p className="text-sm text-zinc-400">
Connect a Telegram bot to receive trading notifications and interact with your traders.
Connect a Telegram bot to receive trading notifications and
interact with your traders.
</p>
<button
onClick={() => setShowTelegramModal(true)}
@@ -512,9 +600,14 @@ export function SettingsPage() {
<div className="w-8 h-8 rounded-lg bg-[#0088cc]/20 flex items-center justify-center">
<MessageCircle size={14} className="text-[#0088cc]" />
</div>
<span className="text-sm font-medium text-white">Configure Telegram Bot</span>
<span className="text-sm font-medium text-white">
Configure Telegram Bot
</span>
</div>
<ChevronRight size={14} className="text-zinc-600 group-hover:text-zinc-400 transition-colors" />
<ChevronRight
size={14}
className="text-zinc-600 group-hover:text-zinc-400 transition-colors"
/>
</button>
</div>
)}
@@ -530,7 +623,10 @@ export function SettingsPage() {
editingModelId={editingModel}
onSave={handleSaveModel}
onDelete={handleDeleteModel}
onClose={() => { setShowModelModal(false); setEditingModel(null) }}
onClose={() => {
setShowModelModal(false)
setEditingModel(null)
}}
language={language}
/>
</div>
@@ -544,7 +640,10 @@ export function SettingsPage() {
editingExchangeId={editingExchange}
onSave={handleSaveExchange}
onDelete={handleDeleteExchange}
onClose={() => { setShowExchangeModal(false); setEditingExchange(null) }}
onClose={() => {
setShowExchangeModal(false)
setEditingExchange(null)
}}
language={language}
/>
</div>