diff --git a/CLAUDE.md b/CLAUDE.md index f3e5d132..dbc264ce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -44,7 +44,7 @@ **前端** - **核心框架**: React 18.3.1 - **类型系统**: TypeScript 5.9.3(渐进式接入中,支持 JS/TS 混合开发) -- **UI 组件库**: Chakra UI 2.8.2(主要) + Ant Design 5.27.4(表格/表单) +- **UI 组件库**: Chakra UI 2.10.9(主要) + Ant Design 5.27.4(表格/表单) - **状态管理**: Redux Toolkit 2.9.2 - **路由**: React Router v6.30.1 配合 React.lazy() 实现代码分割 - **构建系统**: CRACO 7.1.0 + 激进的 webpack 5 优化 diff --git a/package.json b/package.json index a939a72b..f339a183 100755 --- a/package.json +++ b/package.json @@ -6,9 +6,9 @@ "dependencies": { "@ant-design/icons": "^6.0.0", "@asseinfo/react-kanban": "^2.2.0", - "@chakra-ui/icons": "^2.1.1", - "@chakra-ui/react": "^2.8.2", - "@chakra-ui/theme-tools": "^1.3.6", + "@chakra-ui/icons": "^2.2.6", + "@chakra-ui/react": "^2.10.9", + "@chakra-ui/theme-tools": "^2.2.6", "@emotion/cache": "^11.4.0", "@emotion/react": "^11.4.0", "@emotion/styled": "^11.3.0", diff --git a/src/components/Auth/AuthFormContent.js b/src/components/Auth/AuthFormContent.js index 6e808ddf..04b78057 100644 --- a/src/components/Auth/AuthFormContent.js +++ b/src/components/Auth/AuthFormContent.js @@ -356,24 +356,22 @@ export default function AuthFormContent() { // 更新session await checkSession(); + // ✅ 兼容后端两种命名格式:camelCase (isNewUser) 和 snake_case (is_new_user) + const isNewUser = data.isNewUser ?? data.is_new_user ?? false; + // 追踪登录成功并识别用户 - authEvents.trackLoginSuccess(data.user, 'phone', data.isNewUser); + authEvents.trackLoginSuccess(data.user, 'phone', isNewUser); // ✅ 保留登录成功 toast(关键操作提示) toast({ - title: data.isNewUser ? '注册成功' : '登录成功', + title: isNewUser ? '注册成功' : '登录成功', description: config.successDescription, status: "success", duration: 2000, }); - logger.info('AuthFormContent', '登录成功', { - isNewUser: data.isNewUser, - userId: data.user?.id - }); - // 检查是否为新注册用户 - if (data.isNewUser) { + if (isNewUser) { // 新注册用户,延迟后显示昵称设置引导 setTimeout(() => { setCurrentPhone(phone); diff --git a/src/components/Auth/AuthModalManager.js b/src/components/Auth/AuthModalManager.js index d1314ea4..0e0323b9 100644 --- a/src/components/Auth/AuthModalManager.js +++ b/src/components/Auth/AuthModalManager.js @@ -1,5 +1,5 @@ // src/components/Auth/AuthModalManager.js -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { Modal, ModalOverlay, @@ -10,6 +10,8 @@ import { } from '@chakra-ui/react'; import { useAuthModal } from '../../hooks/useAuthModal'; import AuthFormContent from './AuthFormContent'; +import { trackEventAsync } from '@lib/posthog'; +import { ACTIVATION_EVENTS } from '@lib/constants'; /** * 全局认证弹窗管理器 @@ -21,6 +23,27 @@ export default function AuthModalManager() { closeModal } = useAuthModal(); + // ✅ 追踪弹窗打开次数(用于漏斗分析) + const hasTrackedOpen = useRef(false); + + useEffect(() => { + if (isAuthModalOpen && !hasTrackedOpen.current) { + // ✅ 使用异步追踪,不阻塞渲染 + trackEventAsync(ACTIVATION_EVENTS.LOGIN_PAGE_VIEWED, { + timestamp: new Date().toISOString(), + modal_type: 'auth_modal', + trigger_source: 'user_action', // 可以通过 props 传递更精确的来源 + }); + + hasTrackedOpen.current = true; + } + + // ✅ 弹窗关闭时重置标记(允许再次追踪) + if (!isAuthModalOpen) { + hasTrackedOpen.current = false; + } + }, [isAuthModalOpen]); + // 响应式尺寸配置 const modalSize = useBreakpointValue({ base: "md", // 移动端:md(不占满全屏) diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.js index 45b40059..315c05c2 100755 --- a/src/contexts/AuthContext.js +++ b/src/contexts/AuthContext.js @@ -5,6 +5,7 @@ import { useToast } from '@chakra-ui/react'; import { logger } from '../utils/logger'; import { useNotification } from '../contexts/NotificationContext'; import { identifyUser, resetUser, trackEvent } from '@lib/posthog'; +import { SPECIAL_EVENTS } from '@lib/constants'; // 创建认证上下文 const AuthContext = createContext(); @@ -220,25 +221,10 @@ export const AuthProvider = ({ children }) => { setUser(data.user); setIsAuthenticated(true); - // ✅ 追踪登录事件 - trackEvent('user_logged_in', { - loginType, - timestamp: new Date().toISOString() - }); - - // ✅ 首次登录追踪 - const firstLoginKey = `first_login_${data.user.id}`; - const hasLoggedInBefore = localStorage.getItem(firstLoginKey); - - if (!hasLoggedInBefore) { - trackEvent('first_login', { - user_id: data.user.id, - login_type: loginType, - timestamp: new Date().toISOString() - }); - - localStorage.setItem(firstLoginKey, 'true'); - } + // ❌ 过时的追踪代码已移除(新代码在组件中使用 useAuthEvents 追踪) + // 正确的事件追踪在 AuthFormContent.js 中调用 authEvents.trackLoginSuccess() + // 事件名:'User Logged In' 或 'User Signed Up' + // 属性名:login_method (不是 loginType) // ⚡ 移除toast,让调用者处理UI反馈,避免并发更新冲突 // toast({ @@ -294,20 +280,10 @@ export const AuthProvider = ({ children }) => { setUser(data.user); setIsAuthenticated(true); - // ✅ 识别用户身份到 PostHog - identifyUser(data.user.id, { - email: data.user.email, - username: data.user.username, - subscription_tier: data.user.subscription_tier, - role: data.user.role, - registration_date: data.user.created_at - }); - - // ✅ 追踪注册事件 - trackEvent('user_registered', { - method: 'phone', - timestamp: new Date().toISOString() - }); + // ❌ 过时的追踪代码已移除(新代码在组件中使用 useAuthEvents 追踪) + // 正确的事件追踪在 AuthFormContent.js 中调用 authEvents.trackLoginSuccess() + // 事件名:'User Signed Up'(不是 'user_registered') + // 属性名:login_method(不是 method) toast({ title: "注册成功", @@ -332,73 +308,6 @@ export const AuthProvider = ({ children }) => { } }; - // 邮箱注册 - const registerWithEmail = async (email, code, username, password) => { - try { - setIsLoading(true); - - const response = await fetch(`/api/auth/register/email`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - body: JSON.stringify({ - email, - code, - username, - password - }) - }); - - const data = await response.json(); - - if (!response.ok || !data.success) { - throw new Error(data.error || '注册失败'); - } - - // 注册成功后自动登录 - setUser(data.user); - setIsAuthenticated(true); - - // ✅ 识别用户身份到 PostHog - identifyUser(data.user.id, { - email: data.user.email, - username: data.user.username, - subscription_tier: data.user.subscription_tier, - role: data.user.role, - registration_date: data.user.created_at - }); - - // ✅ 追踪注册事件 - trackEvent('user_registered', { - method: 'email', - timestamp: new Date().toISOString() - }); - - toast({ - title: "注册成功", - description: "欢迎加入价值前沿!", - status: "success", - duration: 3000, - isClosable: true, - }); - - // ⚡ 注册成功后显示欢迎引导(延迟2秒) - setTimeout(() => { - showWelcomeGuide(); - }, 2000); - - return { success: true }; - - } catch (error) { - logger.error('AuthContext', 'registerWithEmail', error); - return { success: false, error: error.message }; - } finally { - setIsLoading(false); - } - }; - // 发送手机验证码 const sendSmsCode = async (phone) => { try { @@ -428,35 +337,6 @@ export const AuthProvider = ({ children }) => { } }; - // 发送邮箱验证码 - const sendEmailCode = async (email) => { - try { - const response = await fetch(`/api/auth/send-email-code`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - body: JSON.stringify({ email }) - }); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error || '发送失败'); - } - - // ❌ 移除成功 toast - logger.info('AuthContext', '邮箱验证码已发送', { email: email.substring(0, 3) + '***@***' }); - return { success: true }; - - } catch (error) { - // ❌ 移除错误 toast - logger.error('AuthContext', 'sendEmailCode', error); - return { success: false, error: error.message }; - } - }; - // 登出方法 const logout = async () => { try { @@ -467,8 +347,12 @@ export const AuthProvider = ({ children }) => { }); // ✅ 追踪登出事件(必须在 resetUser() 之前,否则会丢失用户身份) - trackEvent('user_logged_out', { - timestamp: new Date().toISOString() + trackEvent(SPECIAL_EVENTS.USER_LOGGED_OUT, { + timestamp: new Date().toISOString(), + user_id: user?.id || null, + session_duration_minutes: user?.session_start + ? Math.round((Date.now() - new Date(user.session_start).getTime()) / 60000) + : null, }); // ✅ 重置 PostHog 用户会话 @@ -513,9 +397,7 @@ export const AuthProvider = ({ children }) => { updateUser, login, registerWithPhone, - registerWithEmail, sendSmsCode, - sendEmailCode, logout, hasRole, refreshSession, diff --git a/src/index.js b/src/index.js index 9d3dc670..1964cc9b 100755 --- a/src/index.js +++ b/src/index.js @@ -124,6 +124,7 @@ async function startApp() { const root = ReactDOM.createRoot(document.getElementById('root')); // Render the app with Router wrapper + // ✅ StrictMode 已启用(Chakra UI 2.10.9+ 已修复兼容性问题) root.render( { posthog.init(apiKey, { api_host: apiHost, - // Pageview tracking - manual control for better accuracy - capture_pageview: false, // We'll manually capture with custom properties + // Pageview tracking - auto-capture for DAU/MAU analytics + capture_pageview: true, // Auto-capture all page views (required for DAU tracking) capture_pageleave: true, // Auto-capture when user leaves page // Session Recording Configuration diff --git a/src/views/LimitAnalyse/components/EnhancedCalendar.js b/src/views/LimitAnalyse/components/EnhancedCalendar.js index 1cc01759..22a873a3 100644 --- a/src/views/LimitAnalyse/components/EnhancedCalendar.js +++ b/src/views/LimitAnalyse/components/EnhancedCalendar.js @@ -195,9 +195,12 @@ const EnhancedCalendar = ({ onClick={() => onDateChange(date)} transition="all 0.2s" cursor="pointer" + display="flex" + alignItems="center" + justifyContent="center" > @@ -206,13 +209,13 @@ const EnhancedCalendar = ({ {hasData && ( {dateData.count} @@ -221,7 +224,7 @@ const EnhancedCalendar = ({ {isToday && (