feat: 已完成的工作:
- ✅ 创建了4个P1优先级Hook(搜索、导航、个人资料、订阅) - ✅ 将其中3个Hook集成到5个组件中 - ✅ 在个人资料、设置、搜索、订阅流程中添加了15+个追踪点 - ✅ 覆盖了完整的收入漏斗(支付发起 → 成功 → 订阅创建) - ✅ 添加了留存追踪(个人资料更新、设置修改、搜索查询) 影响: - 完整的用户订阅旅程可见性 - 个人资料/设置参与度追踪 - 搜索行为分析 - 完整的支付漏斗追踪(微信支付)
This commit is contained in:
@@ -33,6 +33,7 @@ import {
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
import { useSubscriptionEvents } from '../../hooks/useSubscriptionEvents';
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import {
|
import {
|
||||||
@@ -54,6 +55,14 @@ export default function SubscriptionContent() {
|
|||||||
// Auth context
|
// Auth context
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
// 🎯 初始化订阅埋点Hook(传入当前订阅信息)
|
||||||
|
const subscriptionEvents = useSubscriptionEvents({
|
||||||
|
currentSubscription: {
|
||||||
|
plan: user?.subscription_plan || 'free',
|
||||||
|
status: user?.subscription_status || 'none'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Chakra color mode
|
// Chakra color mode
|
||||||
const textColor = useColorModeValue('gray.700', 'white');
|
const textColor = useColorModeValue('gray.700', 'white');
|
||||||
const borderColor = useColorModeValue('gray.200', 'gray.600');
|
const borderColor = useColorModeValue('gray.200', 'gray.600');
|
||||||
@@ -161,6 +170,13 @@ export default function SubscriptionContent() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🎯 追踪定价方案选择
|
||||||
|
subscriptionEvents.trackPricingPlanSelected(
|
||||||
|
plan.name,
|
||||||
|
selectedCycle,
|
||||||
|
selectedCycle === 'monthly' ? plan.monthly_price : plan.yearly_price
|
||||||
|
);
|
||||||
|
|
||||||
setSelectedPlan(plan);
|
setSelectedPlan(plan);
|
||||||
onPaymentModalOpen();
|
onPaymentModalOpen();
|
||||||
};
|
};
|
||||||
@@ -170,6 +186,17 @@ export default function SubscriptionContent() {
|
|||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
|
const price = selectedCycle === 'monthly' ? selectedPlan.monthly_price : selectedPlan.yearly_price;
|
||||||
|
|
||||||
|
// 🎯 追踪支付发起
|
||||||
|
subscriptionEvents.trackPaymentInitiated({
|
||||||
|
planName: selectedPlan.name,
|
||||||
|
paymentMethod: 'wechat_pay',
|
||||||
|
amount: price,
|
||||||
|
billingCycle: selectedCycle,
|
||||||
|
orderId: null // Will be set after order creation
|
||||||
|
});
|
||||||
|
|
||||||
const response = await fetch('/api/payment/create-order', {
|
const response = await fetch('/api/payment/create-order', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -204,6 +231,13 @@ export default function SubscriptionContent() {
|
|||||||
throw new Error('网络错误');
|
throw new Error('网络错误');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// 🎯 追踪支付失败
|
||||||
|
subscriptionEvents.trackPaymentFailed({
|
||||||
|
planName: selectedPlan.name,
|
||||||
|
paymentMethod: 'wechat_pay',
|
||||||
|
amount: selectedCycle === 'monthly' ? selectedPlan.monthly_price : selectedPlan.yearly_price
|
||||||
|
}, error.message);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: '创建订单失败',
|
title: '创建订单失败',
|
||||||
description: error.message,
|
description: error.message,
|
||||||
@@ -251,6 +285,26 @@ export default function SubscriptionContent() {
|
|||||||
setAutoCheckInterval(null);
|
setAutoCheckInterval(null);
|
||||||
|
|
||||||
logger.info('SubscriptionContent', '自动检测到支付成功', { orderId });
|
logger.info('SubscriptionContent', '自动检测到支付成功', { orderId });
|
||||||
|
|
||||||
|
// 🎯 追踪支付成功
|
||||||
|
subscriptionEvents.trackPaymentSuccessful({
|
||||||
|
planName: selectedPlan?.name,
|
||||||
|
paymentMethod: 'wechat_pay',
|
||||||
|
amount: paymentOrder?.amount,
|
||||||
|
billingCycle: selectedCycle,
|
||||||
|
orderId: orderId,
|
||||||
|
transactionId: data.transaction_id
|
||||||
|
});
|
||||||
|
|
||||||
|
// 🎯 追踪订阅创建
|
||||||
|
subscriptionEvents.trackSubscriptionCreated({
|
||||||
|
plan: selectedPlan?.name,
|
||||||
|
billingCycle: selectedCycle,
|
||||||
|
amount: paymentOrder?.amount,
|
||||||
|
startDate: new Date().toISOString(),
|
||||||
|
endDate: null // Will be calculated by backend
|
||||||
|
});
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: '支付成功!',
|
title: '支付成功!',
|
||||||
description: '订阅已激活,正在跳转...',
|
description: '订阅已激活,正在跳转...',
|
||||||
|
|||||||
@@ -2,11 +2,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, Input, Radio, Form, Button } from 'antd';
|
import { Card, Input, Radio, Form, Button } from 'antd';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import { useSearchEvents } from '../../../hooks/useSearchEvents';
|
||||||
|
|
||||||
const SearchBox = ({ onSearch }) => {
|
const SearchBox = ({ onSearch }) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
// 🎯 初始化搜索埋点Hook
|
||||||
|
const searchEvents = useSearchEvents({ context: 'community' });
|
||||||
|
|
||||||
const handleSubmit = (values) => {
|
const handleSubmit = (values) => {
|
||||||
|
// 🎯 追踪搜索查询提交(在调用onSearch之前)
|
||||||
|
if (values.q) {
|
||||||
|
searchEvents.trackSearchQuerySubmitted(values.q, 0, {
|
||||||
|
search_type: values.search_type || 'topic'
|
||||||
|
});
|
||||||
|
}
|
||||||
onSearch(values);
|
onSearch(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,11 +44,15 @@ import {
|
|||||||
import { EditIcon, CheckIcon, CloseIcon, AddIcon } from '@chakra-ui/icons';
|
import { EditIcon, CheckIcon, CloseIcon, AddIcon } from '@chakra-ui/icons';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
|
import { useProfileEvents } from '../../hooks/useProfileEvents';
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
const { user, updateUser } = useAuth();
|
const { user, updateUser } = useAuth();
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
// 🎯 初始化个人资料埋点Hook
|
||||||
|
const profileEvents = useProfileEvents({ pageType: 'profile' });
|
||||||
const [newTag, setNewTag] = useState('');
|
const [newTag, setNewTag] = useState('');
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const fileInputRef = useRef();
|
const fileInputRef = useRef();
|
||||||
@@ -95,6 +99,12 @@ export default function ProfilePage() {
|
|||||||
updateUser(updatedData);
|
updateUser(updatedData);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
|
|
||||||
|
// 🎯 追踪个人资料更新成功
|
||||||
|
const updatedFields = Object.keys(formData).filter(
|
||||||
|
key => user?.[key] !== formData[key]
|
||||||
|
);
|
||||||
|
profileEvents.trackProfileUpdated(updatedFields, updatedData);
|
||||||
|
|
||||||
// ✅ 保留关键操作提示
|
// ✅ 保留关键操作提示
|
||||||
toast({
|
toast({
|
||||||
title: "个人资料更新成功",
|
title: "个人资料更新成功",
|
||||||
@@ -105,6 +115,10 @@ export default function ProfilePage() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('ProfilePage', 'handleSaveProfile', error, { userId: user?.id });
|
logger.error('ProfilePage', 'handleSaveProfile', error, { userId: user?.id });
|
||||||
|
|
||||||
|
// 🎯 追踪个人资料更新失败
|
||||||
|
const attemptedFields = Object.keys(formData);
|
||||||
|
profileEvents.trackProfileUpdateFailed(attemptedFields, error.message);
|
||||||
|
|
||||||
// ✅ 保留错误提示
|
// ✅ 保留错误提示
|
||||||
toast({
|
toast({
|
||||||
title: "更新失败",
|
title: "更新失败",
|
||||||
@@ -128,6 +142,9 @@ export default function ProfilePage() {
|
|||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
updateUser({ avatar_url: e.target.result });
|
updateUser({ avatar_url: e.target.result });
|
||||||
|
|
||||||
|
// 🎯 追踪头像上传
|
||||||
|
profileEvents.trackAvatarUploaded('file_upload', file.size);
|
||||||
|
|
||||||
// ✅ 保留关键操作提示
|
// ✅ 保留关键操作提示
|
||||||
toast({
|
toast({
|
||||||
title: "头像更新成功",
|
title: "头像更新成功",
|
||||||
|
|||||||
@@ -59,12 +59,16 @@ import { FaWeixin, FaMobile, FaEnvelope } from 'react-icons/fa';
|
|||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
import { getApiBase } from '../../utils/apiConfig';
|
import { getApiBase } from '../../utils/apiConfig';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
|
import { useProfileEvents } from '../../hooks/useProfileEvents';
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const { user, updateUser, logout } = useAuth();
|
const { user, updateUser, logout } = useAuth();
|
||||||
const { colorMode, toggleColorMode } = useColorMode();
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
// 🎯 初始化设置页面埋点Hook
|
||||||
|
const profileEvents = useProfileEvents({ pageType: 'settings' });
|
||||||
|
|
||||||
// 模态框状态
|
// 模态框状态
|
||||||
const { isOpen: isPasswordOpen, onOpen: onPasswordOpen, onClose: onPasswordClose } = useDisclosure();
|
const { isOpen: isPasswordOpen, onOpen: onPasswordOpen, onClose: onPasswordClose } = useDisclosure();
|
||||||
const { isOpen: isPhoneOpen, onOpen: onPhoneOpen, onClose: onPhoneClose } = useDisclosure();
|
const { isOpen: isPhoneOpen, onOpen: onPhoneOpen, onClose: onPhoneClose } = useDisclosure();
|
||||||
@@ -210,6 +214,9 @@ export default function SettingsPage() {
|
|||||||
if (response.ok && data.success) {
|
if (response.ok && data.success) {
|
||||||
const isFirstSet = passwordStatus.needsFirstTimeSetup;
|
const isFirstSet = passwordStatus.needsFirstTimeSetup;
|
||||||
|
|
||||||
|
// 🎯 追踪密码修改成功
|
||||||
|
profileEvents.trackPasswordChanged(true);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: isFirstSet ? "密码设置成功" : "密码修改成功",
|
title: isFirstSet ? "密码设置成功" : "密码修改成功",
|
||||||
description: isFirstSet ? "您现在可以使用手机号+密码登录了" : "请重新登录",
|
description: isFirstSet ? "您现在可以使用手机号+密码登录了" : "请重新登录",
|
||||||
@@ -234,6 +241,9 @@ export default function SettingsPage() {
|
|||||||
throw new Error(data.error || '密码修改失败');
|
throw new Error(data.error || '密码修改失败');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// 🎯 追踪密码修改失败
|
||||||
|
profileEvents.trackPasswordChanged(false, error.message);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "修改失败",
|
title: "修改失败",
|
||||||
description: error.message,
|
description: error.message,
|
||||||
@@ -364,6 +374,9 @@ export default function SettingsPage() {
|
|||||||
email_confirmed: data.user.email_confirmed
|
email_confirmed: data.user.email_confirmed
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🎯 追踪邮箱绑定成功
|
||||||
|
profileEvents.trackAccountBound('email', true);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "邮箱绑定成功",
|
title: "邮箱绑定成功",
|
||||||
status: "success",
|
status: "success",
|
||||||
@@ -374,6 +387,9 @@ export default function SettingsPage() {
|
|||||||
setEmailForm({ email: '', verificationCode: '' });
|
setEmailForm({ email: '', verificationCode: '' });
|
||||||
onEmailClose();
|
onEmailClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// 🎯 追踪邮箱绑定失败
|
||||||
|
profileEvents.trackAccountBound('email', false);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "绑定失败",
|
title: "绑定失败",
|
||||||
description: error.message,
|
description: error.message,
|
||||||
@@ -397,6 +413,13 @@ export default function SettingsPage() {
|
|||||||
|
|
||||||
updateUser(notifications);
|
updateUser(notifications);
|
||||||
|
|
||||||
|
// 🎯 追踪通知偏好更改
|
||||||
|
profileEvents.trackNotificationPreferencesChanged({
|
||||||
|
email: notifications.email_notifications,
|
||||||
|
push: notifications.system_updates,
|
||||||
|
sms: notifications.sms_notifications
|
||||||
|
});
|
||||||
|
|
||||||
// ❌ 移除设置保存成功toast
|
// ❌ 移除设置保存成功toast
|
||||||
logger.info('SettingsPage', '通知设置已保存');
|
logger.info('SettingsPage', '通知设置已保存');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user