diff --git a/src/components/Subscription/SubscriptionContent.js b/src/components/Subscription/SubscriptionContent.js index 7131883f..79c9b351 100644 --- a/src/components/Subscription/SubscriptionContent.js +++ b/src/components/Subscription/SubscriptionContent.js @@ -33,6 +33,7 @@ import { import React, { useState, useEffect } from 'react'; import { logger } from '../../utils/logger'; import { useAuth } from '../../contexts/AuthContext'; +import { useSubscriptionEvents } from '../../hooks/useSubscriptionEvents'; // Icons import { @@ -54,6 +55,14 @@ export default function SubscriptionContent() { // Auth context const { user } = useAuth(); + // 🎯 初始化订阅埋点Hook(传入当前订阅信息) + const subscriptionEvents = useSubscriptionEvents({ + currentSubscription: { + plan: user?.subscription_plan || 'free', + status: user?.subscription_status || 'none' + } + }); + // Chakra color mode const textColor = useColorModeValue('gray.700', 'white'); const borderColor = useColorModeValue('gray.200', 'gray.600'); @@ -161,6 +170,13 @@ export default function SubscriptionContent() { return; } + // 🎯 追踪定价方案选择 + subscriptionEvents.trackPricingPlanSelected( + plan.name, + selectedCycle, + selectedCycle === 'monthly' ? plan.monthly_price : plan.yearly_price + ); + setSelectedPlan(plan); onPaymentModalOpen(); }; @@ -170,6 +186,17 @@ export default function SubscriptionContent() { setLoading(true); 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', { method: 'POST', headers: { @@ -204,6 +231,13 @@ export default function SubscriptionContent() { throw new Error('网络错误'); } } catch (error) { + // 🎯 追踪支付失败 + subscriptionEvents.trackPaymentFailed({ + planName: selectedPlan.name, + paymentMethod: 'wechat_pay', + amount: selectedCycle === 'monthly' ? selectedPlan.monthly_price : selectedPlan.yearly_price + }, error.message); + toast({ title: '创建订单失败', description: error.message, @@ -251,6 +285,26 @@ export default function SubscriptionContent() { setAutoCheckInterval(null); 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({ title: '支付成功!', description: '订阅已激活,正在跳转...', diff --git a/src/views/Community/components/SearchBox.js b/src/views/Community/components/SearchBox.js index 84ff8c43..aa61ccc5 100644 --- a/src/views/Community/components/SearchBox.js +++ b/src/views/Community/components/SearchBox.js @@ -2,11 +2,21 @@ import React from 'react'; import { Card, Input, Radio, Form, Button } from 'antd'; import { SearchOutlined } from '@ant-design/icons'; +import { useSearchEvents } from '../../../hooks/useSearchEvents'; const SearchBox = ({ onSearch }) => { const [form] = Form.useForm(); + // 🎯 初始化搜索埋点Hook + const searchEvents = useSearchEvents({ context: 'community' }); + const handleSubmit = (values) => { + // 🎯 追踪搜索查询提交(在调用onSearch之前) + if (values.q) { + searchEvents.trackSearchQuerySubmitted(values.q, 0, { + search_type: values.search_type || 'topic' + }); + } onSearch(values); }; diff --git a/src/views/Profile/ProfilePage.js b/src/views/Profile/ProfilePage.js index b5099c65..fd2d155e 100644 --- a/src/views/Profile/ProfilePage.js +++ b/src/views/Profile/ProfilePage.js @@ -44,11 +44,15 @@ import { import { EditIcon, CheckIcon, CloseIcon, AddIcon } from '@chakra-ui/icons'; import { useAuth } from '../../contexts/AuthContext'; import { logger } from '../../utils/logger'; +import { useProfileEvents } from '../../hooks/useProfileEvents'; export default function ProfilePage() { const { user, updateUser } = useAuth(); const [isEditing, setIsEditing] = useState(false); const [isLoading, setIsLoading] = useState(false); + + // 🎯 初始化个人资料埋点Hook + const profileEvents = useProfileEvents({ pageType: 'profile' }); const [newTag, setNewTag] = useState(''); const { isOpen, onOpen, onClose } = useDisclosure(); const fileInputRef = useRef(); @@ -95,6 +99,12 @@ export default function ProfilePage() { updateUser(updatedData); setIsEditing(false); + // 🎯 追踪个人资料更新成功 + const updatedFields = Object.keys(formData).filter( + key => user?.[key] !== formData[key] + ); + profileEvents.trackProfileUpdated(updatedFields, updatedData); + // ✅ 保留关键操作提示 toast({ title: "个人资料更新成功", @@ -105,6 +115,10 @@ export default function ProfilePage() { } catch (error) { logger.error('ProfilePage', 'handleSaveProfile', error, { userId: user?.id }); + // 🎯 追踪个人资料更新失败 + const attemptedFields = Object.keys(formData); + profileEvents.trackProfileUpdateFailed(attemptedFields, error.message); + // ✅ 保留错误提示 toast({ title: "更新失败", @@ -128,6 +142,9 @@ export default function ProfilePage() { reader.onload = (e) => { updateUser({ avatar_url: e.target.result }); + // 🎯 追踪头像上传 + profileEvents.trackAvatarUploaded('file_upload', file.size); + // ✅ 保留关键操作提示 toast({ title: "头像更新成功", diff --git a/src/views/Settings/SettingsPage.js b/src/views/Settings/SettingsPage.js index c855b8fc..d592d316 100644 --- a/src/views/Settings/SettingsPage.js +++ b/src/views/Settings/SettingsPage.js @@ -59,12 +59,16 @@ import { FaWeixin, FaMobile, FaEnvelope } from 'react-icons/fa'; import { useAuth } from '../../contexts/AuthContext'; import { getApiBase } from '../../utils/apiConfig'; import { logger } from '../../utils/logger'; +import { useProfileEvents } from '../../hooks/useProfileEvents'; export default function SettingsPage() { const { user, updateUser, logout } = useAuth(); const { colorMode, toggleColorMode } = useColorMode(); const toast = useToast(); + // 🎯 初始化设置页面埋点Hook + const profileEvents = useProfileEvents({ pageType: 'settings' }); + // 模态框状态 const { isOpen: isPasswordOpen, onOpen: onPasswordOpen, onClose: onPasswordClose } = useDisclosure(); const { isOpen: isPhoneOpen, onOpen: onPhoneOpen, onClose: onPhoneClose } = useDisclosure(); @@ -209,9 +213,12 @@ export default function SettingsPage() { if (response.ok && data.success) { const isFirstSet = passwordStatus.needsFirstTimeSetup; - + + // 🎯 追踪密码修改成功 + profileEvents.trackPasswordChanged(true); + toast({ - title: isFirstSet ? "密码设置成功" : "密码修改成功", + title: isFirstSet ? "密码设置成功" : "密码修改成功", description: isFirstSet ? "您现在可以使用手机号+密码登录了" : "请重新登录", status: "success", duration: 3000, @@ -220,7 +227,7 @@ export default function SettingsPage() { setPasswordForm({ currentPassword: '', newPassword: '', confirmPassword: '' }); onPasswordClose(); - + // 刷新密码状态 fetchPasswordStatus(); @@ -234,6 +241,9 @@ export default function SettingsPage() { throw new Error(data.error || '密码修改失败'); } } catch (error) { + // 🎯 追踪密码修改失败 + profileEvents.trackPasswordChanged(false, error.message); + toast({ title: "修改失败", description: error.message, @@ -364,6 +374,9 @@ export default function SettingsPage() { email_confirmed: data.user.email_confirmed }); + // 🎯 追踪邮箱绑定成功 + profileEvents.trackAccountBound('email', true); + toast({ title: "邮箱绑定成功", status: "success", @@ -374,6 +387,9 @@ export default function SettingsPage() { setEmailForm({ email: '', verificationCode: '' }); onEmailClose(); } catch (error) { + // 🎯 追踪邮箱绑定失败 + profileEvents.trackAccountBound('email', false); + toast({ title: "绑定失败", description: error.message, @@ -397,6 +413,13 @@ export default function SettingsPage() { updateUser(notifications); + // 🎯 追踪通知偏好更改 + profileEvents.trackNotificationPreferencesChanged({ + email: notifications.email_notifications, + push: notifications.system_updates, + sms: notifications.sms_notifications + }); + // ❌ 移除设置保存成功toast logger.info('SettingsPage', '通知设置已保存'); } catch (error) {