Merge branch 'feature_2025/251117_pref' of https://git.valuefrontier.cn/vf/vf_react into feature_2025/251117_pref
This commit is contained in:
@@ -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 优化
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(不占满全屏)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
<React.StrictMode>
|
||||
<Router
|
||||
|
||||
@@ -33,8 +33,8 @@ export const initPostHog = () => {
|
||||
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
|
||||
|
||||
@@ -195,9 +195,12 @@ const EnhancedCalendar = ({
|
||||
onClick={() => onDateChange(date)}
|
||||
transition="all 0.2s"
|
||||
cursor="pointer"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Text
|
||||
fontSize={compact ? 'md' : 'lg'}
|
||||
fontSize={compact ? 'lg' : 'xl'}
|
||||
fontWeight={isToday || isSelected ? 'bold' : 'normal'}
|
||||
color={isSelected ? 'blue.600' : 'gray.700'}
|
||||
>
|
||||
@@ -206,13 +209,13 @@ const EnhancedCalendar = ({
|
||||
{hasData && (
|
||||
<Badge
|
||||
position="absolute"
|
||||
top="2px"
|
||||
right="2px"
|
||||
top="4px"
|
||||
right="4px"
|
||||
size={compact ? 'sm' : 'md'}
|
||||
colorScheme={getDateBadgeColor(dateData.count)}
|
||||
fontSize={compact ? '10px' : '11px'}
|
||||
fontSize={compact ? '9px' : '10px'}
|
||||
px={compact ? 1 : 2}
|
||||
minW={compact ? '22px' : '28px'}
|
||||
minW={compact ? '20px' : '24px'}
|
||||
borderRadius="full"
|
||||
>
|
||||
{dateData.count}
|
||||
@@ -221,7 +224,7 @@ const EnhancedCalendar = ({
|
||||
{isToday && (
|
||||
<Text
|
||||
position="absolute"
|
||||
bottom="2px"
|
||||
bottom="4px"
|
||||
left="50%"
|
||||
transform="translateX(-50%)"
|
||||
fontSize={compact ? '9px' : '10px'}
|
||||
|
||||
@@ -444,7 +444,6 @@ export default function LimitAnalyse() {
|
||||
borderColor="whiteAlpha.300"
|
||||
backdropFilter="saturate(180%) blur(10px)"
|
||||
w="full"
|
||||
minH="420px"
|
||||
>
|
||||
<CardBody p={4}>
|
||||
<EnhancedCalendar
|
||||
@@ -453,8 +452,9 @@ export default function LimitAnalyse() {
|
||||
availableDates={availableDates}
|
||||
compact
|
||||
hideSelectionInfo
|
||||
hideLegend
|
||||
width="100%"
|
||||
cellHeight={10}
|
||||
cellHeight={16}
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user