diff --git a/api/server.go b/api/server.go index e21cda6c..99eeb8c0 100644 --- a/api/server.go +++ b/api/server.go @@ -2989,7 +2989,44 @@ func (s *Server) handleRegister(c *gin.Context) { return } - // Check max users limit + var req struct { + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required,min=6"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + SafeBadRequest(c, "Invalid request parameters") + return + } + + // Check if email already exists (must check before maxUsers to allow incomplete OTP users) + existingUser, err := s.store.User().GetByEmail(req.Email) + if err == nil { + // User exists, check OTP verification status + if !existingUser.OTPVerified { + // OTP not verified, verify password first for security + if !auth.CheckPassword(req.Password, existingUser.PasswordHash) { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Email or password incorrect"}) + return + } + // Password correct, allow user to continue OTP setup + // Return existing OTP information + qrCodeURL := auth.GetOTPQRCodeURL(existingUser.OTPSecret, req.Email) + c.JSON(http.StatusOK, gin.H{ + "user_id": existingUser.ID, + "email": existingUser.Email, + "otp_secret": existingUser.OTPSecret, + "qr_code_url": qrCodeURL, + "message": "Incomplete registration detected, please continue OTP setup", + }) + return + } + // OTP already verified, reject duplicate registration + c.JSON(http.StatusConflict, gin.H{"error": "Email already registered"}) + return + } + + // Check max users limit (only for new users) maxUsers := config.Get().MaxUsers if maxUsers > 0 { userCount, err := s.store.User().Count() @@ -3003,23 +3040,6 @@ func (s *Server) handleRegister(c *gin.Context) { } } - var req struct { - Email string `json:"email" binding:"required,email"` - Password string `json:"password" binding:"required,min=6"` - } - - if err := c.ShouldBindJSON(&req); err != nil { - SafeBadRequest(c, "Invalid request parameters") - return - } - - // Check if email already exists - _, err := s.store.User().GetByEmail(req.Email) - if err == nil { - c.JSON(http.StatusConflict, gin.H{"error": "Email already registered"}) - return - } - // Generate password hash passwordHash, err := auth.HashPassword(req.Password) if err != nil { @@ -3141,10 +3161,15 @@ func (s *Server) handleLogin(c *gin.Context) { // Check if OTP is verified if !user.OTPVerified { - c.JSON(http.StatusUnauthorized, gin.H{ - "error": "Account has not completed OTP setup", + // Return OTP info so user can complete setup + qrCodeURL := auth.GetOTPQRCodeURL(user.OTPSecret, user.Email) + c.JSON(http.StatusOK, gin.H{ "user_id": user.ID, + "email": user.Email, + "otp_secret": user.OTPSecret, + "qr_code_url": qrCodeURL, "requires_otp_setup": true, + "message": "Please complete OTP setup first", }) return } diff --git a/web/src/components/AdvancedChart.tsx b/web/src/components/AdvancedChart.tsx index 5eb372cd..b6d4108d 100644 --- a/web/src/components/AdvancedChart.tsx +++ b/web/src/components/AdvancedChart.tsx @@ -98,7 +98,6 @@ export function AdvancedChart({ symbol = 'BTCUSDT', interval = '5m', traderID, - height = 550, exchange = 'binance', // 默认使用 binance onSymbolChange: _onSymbolChange, // Available for future use }: AdvancedChartProps) { @@ -347,7 +346,7 @@ export function AdvancedChart({ const chart = createChart(chartContainerRef.current, { width: chartContainerRef.current.clientWidth, - height: height, + height: chartContainerRef.current.clientHeight, // Use container height layout: { background: { color: '#0B0E11' }, textColor: '#B7BDC6', @@ -447,16 +446,16 @@ export function AdvancedChart({ }) volumeSeriesRef.current = volumeSeries as any - // 响应式调整 - const handleResize = () => { - if (chartContainerRef.current && chartRef.current) { - chartRef.current.applyOptions({ - width: chartContainerRef.current.clientWidth, - }) - } - } + // 响应式调整 (ResizeObserver) + const resizeObserver = new ResizeObserver((entries) => { + if (entries.length === 0 || !entries[0].contentRect) return + const { width, height } = entries[0].contentRect + chart.applyOptions({ width, height }) + }) - window.addEventListener('resize', handleResize) + if (chartContainerRef.current) { + resizeObserver.observe(chartContainerRef.current) + } // 监听鼠标移动,显示 OHLC 信息 chart.subscribeCrosshairMove((param) => { @@ -490,10 +489,11 @@ export function AdvancedChart({ }) return () => { - window.removeEventListener('resize', handleResize) + resizeObserver.disconnect() chart.remove() } - }, [height]) + }, []) // Removed [height] dependency as we now autosize + // 加载数据和指标 useEffect(() => { diff --git a/web/src/components/BacktestPage.tsx b/web/src/components/BacktestPage.tsx index ea6ac5a0..70ffa6f3 100644 --- a/web/src/components/BacktestPage.tsx +++ b/web/src/components/BacktestPage.tsx @@ -1486,7 +1486,7 @@ export function BacktestPage() { -
+