feat: Implement initial full-stack application structure including frontend pages, components, hooks, API integration, and backend services for account pooling and management.
This commit is contained in:
148
frontend/src/context/ConfigContext.tsx
Normal file
148
frontend/src/context/ConfigContext.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'
|
||||
import type { AppConfig } from '../types'
|
||||
import { defaultConfig } from '../types'
|
||||
import { loadConfig, saveConfig } from '../utils/storage'
|
||||
import { S2AClient } from '../api/s2a'
|
||||
|
||||
interface ConfigContextValue {
|
||||
config: AppConfig
|
||||
updateConfig: (updates: Partial<AppConfig>) => void
|
||||
updateS2AConfig: (updates: Partial<AppConfig['s2a']>) => void
|
||||
updatePoolingConfig: (updates: Partial<AppConfig['pooling']>) => void
|
||||
updateCheckConfig: (updates: Partial<AppConfig['check']>) => void
|
||||
updateEmailConfig: (updates: Partial<AppConfig['email']>) => void
|
||||
isConnected: boolean
|
||||
testConnection: () => Promise<boolean>
|
||||
s2aClient: S2AClient | null
|
||||
}
|
||||
|
||||
const ConfigContext = createContext<ConfigContextValue | null>(null)
|
||||
|
||||
export function ConfigProvider({ children }: { children: ReactNode }) {
|
||||
const [config, setConfig] = useState<AppConfig>(defaultConfig)
|
||||
const [isConnected, setIsConnected] = useState(false)
|
||||
const [s2aClient, setS2aClient] = useState<S2AClient | null>(null)
|
||||
|
||||
// Load config from localStorage on mount
|
||||
useEffect(() => {
|
||||
const savedConfig = loadConfig()
|
||||
setConfig(savedConfig)
|
||||
|
||||
// Create S2A client if config is available
|
||||
if (savedConfig.s2a.apiBase && savedConfig.s2a.adminKey) {
|
||||
const client = new S2AClient({
|
||||
baseUrl: savedConfig.s2a.apiBase,
|
||||
apiKey: savedConfig.s2a.adminKey,
|
||||
})
|
||||
setS2aClient(client)
|
||||
|
||||
// Test connection on load
|
||||
client.testConnection().then(setIsConnected)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Update S2A client when config changes
|
||||
useEffect(() => {
|
||||
if (config.s2a.apiBase && config.s2a.adminKey) {
|
||||
const client = new S2AClient({
|
||||
baseUrl: config.s2a.apiBase,
|
||||
apiKey: config.s2a.adminKey,
|
||||
})
|
||||
setS2aClient(client)
|
||||
} else {
|
||||
setS2aClient(null)
|
||||
setIsConnected(false)
|
||||
}
|
||||
}, [config.s2a.apiBase, config.s2a.adminKey])
|
||||
|
||||
const updateConfig = useCallback((updates: Partial<AppConfig>) => {
|
||||
setConfig((prev) => {
|
||||
const newConfig = { ...prev, ...updates }
|
||||
saveConfig(newConfig)
|
||||
return newConfig
|
||||
})
|
||||
}, [])
|
||||
|
||||
const updateS2AConfig = useCallback((updates: Partial<AppConfig['s2a']>) => {
|
||||
setConfig((prev) => {
|
||||
const newConfig = {
|
||||
...prev,
|
||||
s2a: { ...prev.s2a, ...updates },
|
||||
}
|
||||
saveConfig(newConfig)
|
||||
return newConfig
|
||||
})
|
||||
}, [])
|
||||
|
||||
const updatePoolingConfig = useCallback((updates: Partial<AppConfig['pooling']>) => {
|
||||
setConfig((prev) => {
|
||||
const newConfig = {
|
||||
...prev,
|
||||
pooling: { ...prev.pooling, ...updates },
|
||||
}
|
||||
saveConfig(newConfig)
|
||||
return newConfig
|
||||
})
|
||||
}, [])
|
||||
|
||||
const updateCheckConfig = useCallback((updates: Partial<AppConfig['check']>) => {
|
||||
setConfig((prev) => {
|
||||
const newConfig = {
|
||||
...prev,
|
||||
check: { ...prev.check, ...updates },
|
||||
}
|
||||
saveConfig(newConfig)
|
||||
return newConfig
|
||||
})
|
||||
}, [])
|
||||
|
||||
const updateEmailConfig = useCallback((updates: Partial<AppConfig['email']>) => {
|
||||
setConfig((prev) => {
|
||||
const newConfig = {
|
||||
...prev,
|
||||
email: { ...prev.email, ...updates },
|
||||
}
|
||||
saveConfig(newConfig)
|
||||
return newConfig
|
||||
})
|
||||
}, [])
|
||||
|
||||
const testConnection = useCallback(async (): Promise<boolean> => {
|
||||
try {
|
||||
// 使用后端代理 API 来测试 S2A 连接(避免 CORS 问题)
|
||||
const res = await fetch('http://localhost:8088/api/s2a/test')
|
||||
const connected = res.ok
|
||||
setIsConnected(connected)
|
||||
return connected
|
||||
} catch {
|
||||
setIsConnected(false)
|
||||
return false
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ConfigContext.Provider
|
||||
value={{
|
||||
config,
|
||||
updateConfig,
|
||||
updateS2AConfig,
|
||||
updatePoolingConfig,
|
||||
updateCheckConfig,
|
||||
updateEmailConfig,
|
||||
isConnected,
|
||||
testConnection,
|
||||
s2aClient,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ConfigContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useConfigContext(): ConfigContextValue {
|
||||
const context = useContext(ConfigContext)
|
||||
if (!context) {
|
||||
throw new Error('useConfigContext must be used within a ConfigProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
Reference in New Issue
Block a user