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:
2026-01-30 07:40:35 +08:00
commit f4448bbef2
106 changed files with 19282 additions and 0 deletions

View 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
}