mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 01:48:22 +08:00
merge: resolve conflicts from origin/main into dev
All conflicts were in frontend files where main had beginner-mode features (BeginnerGuideCards, Claw402 balance alerts, mode switcher, actionable error helpers) that dev intentionally simplified. Kept dev's version in every case. Removed unused navigate import in SettingsPage after conflict resolution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -217,7 +217,7 @@ func (s *Server) handleGetSupportedModels(c *gin.Context) {
|
|||||||
{"id": "qwen", "name": "Qwen", "provider": "qwen", "defaultModel": "qwen3-max"},
|
{"id": "qwen", "name": "Qwen", "provider": "qwen", "defaultModel": "qwen3-max"},
|
||||||
{"id": "openai", "name": "OpenAI", "provider": "openai", "defaultModel": "gpt-5.1"},
|
{"id": "openai", "name": "OpenAI", "provider": "openai", "defaultModel": "gpt-5.1"},
|
||||||
{"id": "claude", "name": "Claude", "provider": "claude", "defaultModel": "claude-opus-4-6"},
|
{"id": "claude", "name": "Claude", "provider": "claude", "defaultModel": "claude-opus-4-6"},
|
||||||
{"id": "gemini", "name": "Google Gemini", "provider": "gemini", "defaultModel": "gemini-3-pro-preview"},
|
{"id": "gemini", "name": "Google Gemini", "provider": "gemini", "defaultModel": "gemini-3.1-pro"},
|
||||||
{"id": "grok", "name": "Grok (xAI)", "provider": "grok", "defaultModel": "grok-3-latest"},
|
{"id": "grok", "name": "Grok (xAI)", "provider": "grok", "defaultModel": "grok-3-latest"},
|
||||||
{"id": "kimi", "name": "Kimi (Moonshot)", "provider": "kimi", "defaultModel": "moonshot-v1-auto"},
|
{"id": "kimi", "name": "Kimi (Moonshot)", "provider": "kimi", "defaultModel": "moonshot-v1-auto"},
|
||||||
{"id": "minimax", "name": "MiniMax", "provider": "minimax", "defaultModel": "MiniMax-M2.7"},
|
{"id": "minimax", "name": "MiniMax", "provider": "minimax", "defaultModel": "MiniMax-M2.7"},
|
||||||
|
|||||||
@@ -45,8 +45,12 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
const [allModels, setAllModels] = useState<AIModel[]>([])
|
const [allModels, setAllModels] = useState<AIModel[]>([])
|
||||||
const [allExchanges, setAllExchanges] = useState<Exchange[]>([])
|
const [allExchanges, setAllExchanges] = useState<Exchange[]>([])
|
||||||
const [supportedModels, setSupportedModels] = useState<AIModel[]>([])
|
const [supportedModels, setSupportedModels] = useState<AIModel[]>([])
|
||||||
const [visibleTraderAddresses, setVisibleTraderAddresses] = useState<Set<string>>(new Set())
|
const [visibleTraderAddresses, setVisibleTraderAddresses] = useState<
|
||||||
const [visibleExchangeAddresses, setVisibleExchangeAddresses] = useState<Set<string>>(new Set())
|
Set<string>
|
||||||
|
>(new Set())
|
||||||
|
const [visibleExchangeAddresses, setVisibleExchangeAddresses] = useState<
|
||||||
|
Set<string>
|
||||||
|
>(new Set())
|
||||||
const [copiedId, setCopiedId] = useState<string | null>(null)
|
const [copiedId, setCopiedId] = useState<string | null>(null)
|
||||||
|
|
||||||
const loadConfigs = async () => {
|
const loadConfigs = async () => {
|
||||||
@@ -72,7 +76,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
|
|
||||||
// Toggle wallet address visibility for a trader
|
// Toggle wallet address visibility for a trader
|
||||||
const toggleTraderAddressVisibility = (traderId: string) => {
|
const toggleTraderAddressVisibility = (traderId: string) => {
|
||||||
setVisibleTraderAddresses(prev => {
|
setVisibleTraderAddresses((prev) => {
|
||||||
const next = new Set(prev)
|
const next = new Set(prev)
|
||||||
if (next.has(traderId)) {
|
if (next.has(traderId)) {
|
||||||
next.delete(traderId)
|
next.delete(traderId)
|
||||||
@@ -85,7 +89,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
|
|
||||||
// Toggle wallet address visibility for an exchange
|
// Toggle wallet address visibility for an exchange
|
||||||
const toggleExchangeAddressVisibility = (exchangeId: string) => {
|
const toggleExchangeAddressVisibility = (exchangeId: string) => {
|
||||||
setVisibleExchangeAddresses(prev => {
|
setVisibleExchangeAddresses((prev) => {
|
||||||
const next = new Set(prev)
|
const next = new Set(prev)
|
||||||
if (next.has(exchangeId)) {
|
if (next.has(exchangeId)) {
|
||||||
next.delete(exchangeId)
|
next.delete(exchangeId)
|
||||||
@@ -180,7 +184,8 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getExchangeUsageInfo = (exchangeId: string) => {
|
const getExchangeUsageInfo = (exchangeId: string) => {
|
||||||
const usingTraders = traders?.filter((tr) => tr.exchange_id === exchangeId) || []
|
const usingTraders =
|
||||||
|
traders?.filter((tr) => tr.exchange_id === exchangeId) || []
|
||||||
const runningCount = usingTraders.filter((tr) => tr.is_running).length
|
const runningCount = usingTraders.filter((tr) => tr.is_running).length
|
||||||
const totalCount = usingTraders.length
|
const totalCount = usingTraders.length
|
||||||
return { runningCount, totalCount, usingTraders }
|
return { runningCount, totalCount, usingTraders }
|
||||||
@@ -311,11 +316,18 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleToggleCompetition = async (traderId: string, currentShowInCompetition: boolean) => {
|
const handleToggleCompetition = async (
|
||||||
|
traderId: string,
|
||||||
|
currentShowInCompetition: boolean
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const newValue = !currentShowInCompetition
|
const newValue = !currentShowInCompetition
|
||||||
await api.toggleCompetition(traderId, newValue)
|
await api.toggleCompetition(traderId, newValue)
|
||||||
toast.success(newValue ? t('aiTradersToast.showInCompetition', language) : t('aiTradersToast.hideInCompetition', language))
|
toast.success(
|
||||||
|
newValue
|
||||||
|
? t('aiTradersToast.showInCompetition', language)
|
||||||
|
: t('aiTradersToast.hideInCompetition', language)
|
||||||
|
)
|
||||||
|
|
||||||
await mutateTraders()
|
await mutateTraders()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -452,12 +464,12 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
allModels?.map((m) =>
|
allModels?.map((m) =>
|
||||||
m.id === modelId
|
m.id === modelId
|
||||||
? {
|
? {
|
||||||
...m,
|
...m,
|
||||||
apiKey,
|
apiKey,
|
||||||
customApiUrl: customApiUrl || '',
|
customApiUrl: customApiUrl || '',
|
||||||
customModelName: customModelName || '',
|
customModelName: customModelName || '',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
}
|
}
|
||||||
: m
|
: m
|
||||||
) || []
|
) || []
|
||||||
} else {
|
} else {
|
||||||
@@ -572,7 +584,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await api.updateExchangeConfigsEncrypted(request)
|
await api.updateExchangeConfigsEncrypted(request)
|
||||||
toast.success(t('aiTradersToast.exchangeConfigUpdated', language))
|
toast.success(t('aiTradersToast.exchangeConfigUpdated', language))
|
||||||
} else {
|
} else {
|
||||||
const createRequest = {
|
const createRequest = {
|
||||||
exchange_type: exchangeType,
|
exchange_type: exchangeType,
|
||||||
@@ -593,7 +605,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await api.createExchangeEncrypted(createRequest)
|
await api.createExchangeEncrypted(createRequest)
|
||||||
toast.success(t('aiTradersToast.exchangeCreated', language))
|
toast.success(t('aiTradersToast.exchangeCreated', language))
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshedExchanges = await api.getExchangeConfigs()
|
const refreshedExchanges = await api.getExchangeConfigs()
|
||||||
@@ -676,7 +688,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowCreateModal(true)}
|
onClick={() => setShowCreateModal(true)}
|
||||||
disabled={configuredModels.length === 0 || configuredExchanges.length === 0}
|
disabled={
|
||||||
|
configuredModels.length === 0 ||
|
||||||
|
configuredExchanges.length === 0
|
||||||
|
}
|
||||||
className="group relative px-6 py-2 rounded text-xs font-bold font-mono uppercase tracking-wider transition-all disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap overflow-hidden bg-nofx-gold text-black hover:bg-yellow-400 shadow-[0_0_20px_rgba(240,185,11,0.2)] hover:shadow-[0_0_30px_rgba(240,185,11,0.4)]"
|
className="group relative px-6 py-2 rounded text-xs font-bold font-mono uppercase tracking-wider transition-all disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap overflow-hidden bg-nofx-gold text-black hover:bg-yellow-400 shadow-[0_0_20px_rgba(240,185,11,0.2)] hover:shadow-[0_0_30px_rgba(240,185,11,0.4)]"
|
||||||
>
|
>
|
||||||
<span className="relative z-10 flex items-center gap-2">
|
<span className="relative z-10 flex items-center gap-2">
|
||||||
|
|||||||
+128
-47
@@ -1,6 +1,16 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { toast } from 'sonner'
|
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 { useAuth } from '../contexts/AuthContext'
|
||||||
import { useLanguage } from '../contexts/LanguageContext'
|
import { useLanguage } from '../contexts/LanguageContext'
|
||||||
import { api } from '../lib/api'
|
import { api } from '../lib/api'
|
||||||
@@ -107,7 +117,9 @@ export function SettingsPage() {
|
|||||||
toast.success('Password updated successfully')
|
toast.success('Password updated successfully')
|
||||||
setNewPassword('')
|
setNewPassword('')
|
||||||
} catch (err) {
|
} 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 {
|
} finally {
|
||||||
setChangingPassword(false)
|
setChangingPassword(false)
|
||||||
}
|
}
|
||||||
@@ -123,33 +135,48 @@ export function SettingsPage() {
|
|||||||
const existingModel = configuredModels.find((m) => m.id === modelId)
|
const existingModel = configuredModels.find((m) => m.id === modelId)
|
||||||
const modelTemplate = supportedModels.find((m) => m.id === modelId)
|
const modelTemplate = supportedModels.find((m) => m.id === modelId)
|
||||||
const modelToUpdate = existingModel || modelTemplate
|
const modelToUpdate = existingModel || modelTemplate
|
||||||
if (!modelToUpdate) { toast.error('Model not found'); return }
|
if (!modelToUpdate) {
|
||||||
|
toast.error('Model not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let updatedModels: AIModel[]
|
let updatedModels: AIModel[]
|
||||||
if (existingModel) {
|
if (existingModel) {
|
||||||
updatedModels = configuredModels.map((m) =>
|
updatedModels = configuredModels.map((m) =>
|
||||||
m.id === modelId
|
m.id === modelId
|
||||||
? { ...m, apiKey, customApiUrl: customApiUrl || '', customModelName: customModelName || '', enabled: true }
|
? {
|
||||||
|
...m,
|
||||||
|
apiKey,
|
||||||
|
customApiUrl: customApiUrl || '',
|
||||||
|
customModelName: customModelName || '',
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
: m
|
: m
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
updatedModels = [...configuredModels, {
|
updatedModels = [
|
||||||
...modelToUpdate,
|
...configuredModels,
|
||||||
apiKey,
|
{
|
||||||
customApiUrl: customApiUrl || '',
|
...modelToUpdate,
|
||||||
customModelName: customModelName || '',
|
apiKey,
|
||||||
enabled: true,
|
customApiUrl: customApiUrl || '',
|
||||||
}]
|
customModelName: customModelName || '',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
models: Object.fromEntries(
|
models: Object.fromEntries(
|
||||||
updatedModels.map((m) => [m.provider, {
|
updatedModels.map((m) => [
|
||||||
enabled: m.enabled,
|
m.provider,
|
||||||
api_key: m.apiKey || '',
|
{
|
||||||
custom_api_url: m.customApiUrl || '',
|
enabled: m.enabled,
|
||||||
custom_model_name: m.customModelName || '',
|
api_key: m.apiKey || '',
|
||||||
}])
|
custom_api_url: m.customApiUrl || '',
|
||||||
|
custom_model_name: m.customModelName || '',
|
||||||
|
},
|
||||||
|
])
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
await api.updateModelConfigs(request)
|
await api.updateModelConfigs(request)
|
||||||
@@ -165,16 +192,27 @@ export function SettingsPage() {
|
|||||||
const handleDeleteModel = async (modelId: string) => {
|
const handleDeleteModel = async (modelId: string) => {
|
||||||
try {
|
try {
|
||||||
const updatedModels = configuredModels.map((m) =>
|
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 = {
|
const request = {
|
||||||
models: Object.fromEntries(
|
models: Object.fromEntries(
|
||||||
updatedModels.map((m) => [m.provider, {
|
updatedModels.map((m) => [
|
||||||
enabled: m.enabled,
|
m.provider,
|
||||||
api_key: m.apiKey || '',
|
{
|
||||||
custom_api_url: m.customApiUrl || '',
|
enabled: m.enabled,
|
||||||
custom_model_name: m.customModelName || '',
|
api_key: m.apiKey || '',
|
||||||
}])
|
custom_api_url: m.customApiUrl || '',
|
||||||
|
custom_model_name: m.customModelName || '',
|
||||||
|
},
|
||||||
|
])
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
await api.updateModelConfigs(request)
|
await api.updateModelConfigs(request)
|
||||||
@@ -226,7 +264,7 @@ export function SettingsPage() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
await api.updateExchangeConfigsEncrypted(request)
|
await api.updateExchangeConfigsEncrypted(request)
|
||||||
toast.success('Exchange config updated')
|
toast.success('Exchange config updated')
|
||||||
} else {
|
} else {
|
||||||
const createRequest = {
|
const createRequest = {
|
||||||
exchange_type: exchangeType,
|
exchange_type: exchangeType,
|
||||||
@@ -246,7 +284,7 @@ export function SettingsPage() {
|
|||||||
lighter_api_key_index: lighterApiKeyIndex || 0,
|
lighter_api_key_index: lighterApiKeyIndex || 0,
|
||||||
}
|
}
|
||||||
await api.createExchangeEncrypted(createRequest)
|
await api.createExchangeEncrypted(createRequest)
|
||||||
toast.success('Exchange account created')
|
toast.success('Exchange account created')
|
||||||
}
|
}
|
||||||
await refreshExchangeConfigs()
|
await refreshExchangeConfigs()
|
||||||
setShowExchangeModal(false)
|
setShowExchangeModal(false)
|
||||||
@@ -276,7 +314,10 @@ export function SettingsPage() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
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">
|
<div className="max-w-2xl mx-auto">
|
||||||
<h1 className="text-xl font-bold text-white mb-6">Settings</h1>
|
<h1 className="text-xl font-bold text-white mb-6">Settings</h1>
|
||||||
|
|
||||||
@@ -287,9 +328,10 @@ export function SettingsPage() {
|
|||||||
key={tab.key}
|
key={tab.key}
|
||||||
onClick={() => setActiveTab(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
|
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'
|
activeTab === tab.key
|
||||||
: 'text-zinc-400 hover:text-white'
|
? 'bg-nofx-gold text-black'
|
||||||
|
: 'text-zinc-400 hover:text-white'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{tab.icon}
|
{tab.icon}
|
||||||
@@ -300,7 +342,6 @@ export function SettingsPage() {
|
|||||||
|
|
||||||
{/* Tab Content */}
|
{/* Tab Content */}
|
||||||
<div className="bg-zinc-900/60 backdrop-blur-xl border border-zinc-800/80 rounded-2xl p-6">
|
<div className="bg-zinc-900/60 backdrop-blur-xl border border-zinc-800/80 rounded-2xl p-6">
|
||||||
|
|
||||||
{/* Account Tab */}
|
{/* Account Tab */}
|
||||||
{activeTab === 'account' && (
|
{activeTab === 'account' && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -313,7 +354,9 @@ export function SettingsPage() {
|
|||||||
<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">
|
<form onSubmit={handleChangePassword} className="space-y-4">
|
||||||
<div>
|
<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">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
@@ -328,7 +371,11 @@ export function SettingsPage() {
|
|||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
className="absolute right-3.5 top-1/2 -translate-y-1/2 text-zinc-500 hover:text-zinc-300 transition-colors"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -349,10 +396,14 @@ export function SettingsPage() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-zinc-400">
|
||||||
{configuredModels.length} model{configuredModels.length !== 1 ? 's' : ''} configured
|
{configuredModels.length} model
|
||||||
|
{configuredModels.length !== 1 ? 's' : ''} configured
|
||||||
</p>
|
</p>
|
||||||
<button
|
<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"
|
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} />
|
<Plus size={14} />
|
||||||
@@ -369,7 +420,10 @@ export function SettingsPage() {
|
|||||||
{configuredModels.map((model) => (
|
{configuredModels.map((model) => (
|
||||||
<button
|
<button
|
||||||
key={model.id}
|
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"
|
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">
|
<div className="flex items-center gap-3">
|
||||||
@@ -387,10 +441,15 @@ export function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<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'}
|
{model.enabled ? 'Active' : 'Inactive'}
|
||||||
</span>
|
</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>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -404,10 +463,14 @@ export function SettingsPage() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-sm text-zinc-400">
|
<p className="text-sm text-zinc-400">
|
||||||
{exchanges.length} account{exchanges.length !== 1 ? 's' : ''} connected
|
{exchanges.length} account{exchanges.length !== 1 ? 's' : ''}{' '}
|
||||||
|
connected
|
||||||
</p>
|
</p>
|
||||||
<button
|
<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"
|
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} />
|
<Plus size={14} />
|
||||||
@@ -424,7 +487,10 @@ export function SettingsPage() {
|
|||||||
{exchanges.map((exchange) => (
|
{exchanges.map((exchange) => (
|
||||||
<button
|
<button
|
||||||
key={exchange.id}
|
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"
|
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">
|
<div className="flex items-center gap-3">
|
||||||
@@ -444,7 +510,10 @@ export function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -456,7 +525,8 @@ export function SettingsPage() {
|
|||||||
{activeTab === 'telegram' && (
|
{activeTab === 'telegram' && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-sm text-zinc-400">
|
<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>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowTelegramModal(true)}
|
onClick={() => setShowTelegramModal(true)}
|
||||||
@@ -466,9 +536,14 @@ export function SettingsPage() {
|
|||||||
<div className="w-8 h-8 rounded-lg bg-[#0088cc]/20 flex items-center justify-center">
|
<div className="w-8 h-8 rounded-lg bg-[#0088cc]/20 flex items-center justify-center">
|
||||||
<MessageCircle size={14} className="text-[#0088cc]" />
|
<MessageCircle size={14} className="text-[#0088cc]" />
|
||||||
</div>
|
</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>
|
</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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -484,7 +559,10 @@ export function SettingsPage() {
|
|||||||
editingModelId={editingModel}
|
editingModelId={editingModel}
|
||||||
onSave={handleSaveModel}
|
onSave={handleSaveModel}
|
||||||
onDelete={handleDeleteModel}
|
onDelete={handleDeleteModel}
|
||||||
onClose={() => { setShowModelModal(false); setEditingModel(null) }}
|
onClose={() => {
|
||||||
|
setShowModelModal(false)
|
||||||
|
setEditingModel(null)
|
||||||
|
}}
|
||||||
language={language}
|
language={language}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -498,7 +576,10 @@ export function SettingsPage() {
|
|||||||
editingExchangeId={editingExchange}
|
editingExchangeId={editingExchange}
|
||||||
onSave={handleSaveExchange}
|
onSave={handleSaveExchange}
|
||||||
onDelete={handleDeleteExchange}
|
onDelete={handleDeleteExchange}
|
||||||
onClose={() => { setShowExchangeModal(false); setEditingExchange(null) }}
|
onClose={() => {
|
||||||
|
setShowExchangeModal(false)
|
||||||
|
setEditingExchange(null)
|
||||||
|
}}
|
||||||
language={language}
|
language={language}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user