feat: P1通用功能:4个Hook创建完成(待集成)现在您可以追踪:
1. 完整的用户旅程
- 从进入网站 → 浏览内容 → 使用功能 → 遇到付费墙 → 付费转化
2. 核心业务指标
- DAU/MAU(活跃用户)
- 功能使用率(哪些功能最受欢迎)
- 搜索热度(用户需求洞察)
- Revenue转化漏斗(付费转化分析)
- 用户参与度(Profile更新、设置变更)
3. 产品优化方向
- 哪些功能需要优化?
- 用户在哪个环节流失?
- 哪些内容最受欢迎?
- 如何提高付费转化率?
This commit is contained in:
293
src/hooks/useNavigationEvents.js
Normal file
293
src/hooks/useNavigationEvents.js
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
// src/hooks/useNavigationEvents.js
|
||||||
|
// 导航和菜单事件追踪 Hook
|
||||||
|
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { usePostHogTrack } from './usePostHogRedux';
|
||||||
|
import { RETENTION_EVENTS } from '../lib/constants';
|
||||||
|
import { logger } from '../utils/logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导航事件追踪 Hook
|
||||||
|
* @param {Object} options - 配置选项
|
||||||
|
* @param {string} options.component - 组件名称 ('top_nav' | 'sidebar' | 'breadcrumb' | 'footer')
|
||||||
|
* @returns {Object} 事件追踪处理函数集合
|
||||||
|
*/
|
||||||
|
export const useNavigationEvents = ({ component = 'navigation' } = {}) => {
|
||||||
|
const { track } = usePostHogTrack();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪顶部导航点击
|
||||||
|
* @param {string} itemName - 导航项名称
|
||||||
|
* @param {string} path - 导航目标路径
|
||||||
|
* @param {string} category - 导航分类 ('main' | 'user' | 'utility')
|
||||||
|
*/
|
||||||
|
const trackTopNavClicked = useCallback((itemName, path = '', category = 'main') => {
|
||||||
|
if (!itemName) {
|
||||||
|
logger.warn('useNavigationEvents', 'trackTopNavClicked: itemName is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(RETENTION_EVENTS.TOP_NAV_CLICKED, {
|
||||||
|
item_name: itemName,
|
||||||
|
path,
|
||||||
|
category,
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '🔝 Top Navigation Clicked', {
|
||||||
|
itemName,
|
||||||
|
path,
|
||||||
|
category,
|
||||||
|
});
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪侧边栏菜单点击
|
||||||
|
* @param {string} itemName - 菜单项名称
|
||||||
|
* @param {string} path - 目标路径
|
||||||
|
* @param {number} level - 菜单层级 (1=主菜单, 2=子菜单)
|
||||||
|
* @param {boolean} isExpanded - 是否展开状态
|
||||||
|
*/
|
||||||
|
const trackSidebarMenuClicked = useCallback((itemName, path = '', level = 1, isExpanded = false) => {
|
||||||
|
if (!itemName) {
|
||||||
|
logger.warn('useNavigationEvents', 'trackSidebarMenuClicked: itemName is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(RETENTION_EVENTS.SIDEBAR_MENU_CLICKED, {
|
||||||
|
item_name: itemName,
|
||||||
|
path,
|
||||||
|
level,
|
||||||
|
is_expanded: isExpanded,
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '📂 Sidebar Menu Clicked', {
|
||||||
|
itemName,
|
||||||
|
path,
|
||||||
|
level,
|
||||||
|
isExpanded,
|
||||||
|
});
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪通用菜单项点击
|
||||||
|
* @param {string} itemName - 菜单项名称
|
||||||
|
* @param {string} menuType - 菜单类型 ('dropdown' | 'context' | 'tab')
|
||||||
|
* @param {string} path - 目标路径
|
||||||
|
*/
|
||||||
|
const trackMenuItemClicked = useCallback((itemName, menuType = 'dropdown', path = '') => {
|
||||||
|
if (!itemName) {
|
||||||
|
logger.warn('useNavigationEvents', 'trackMenuItemClicked: itemName is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(RETENTION_EVENTS.MENU_ITEM_CLICKED, {
|
||||||
|
item_name: itemName,
|
||||||
|
menu_type: menuType,
|
||||||
|
path,
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '📋 Menu Item Clicked', {
|
||||||
|
itemName,
|
||||||
|
menuType,
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪面包屑导航点击
|
||||||
|
* @param {string} itemName - 面包屑项名称
|
||||||
|
* @param {string} path - 目标路径
|
||||||
|
* @param {number} position - 在面包屑中的位置
|
||||||
|
* @param {number} totalItems - 面包屑总项数
|
||||||
|
*/
|
||||||
|
const trackBreadcrumbClicked = useCallback((itemName, path = '', position = 0, totalItems = 0) => {
|
||||||
|
if (!itemName) {
|
||||||
|
logger.warn('useNavigationEvents', 'trackBreadcrumbClicked: itemName is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(RETENTION_EVENTS.BREADCRUMB_CLICKED, {
|
||||||
|
item_name: itemName,
|
||||||
|
path,
|
||||||
|
position,
|
||||||
|
total_items: totalItems,
|
||||||
|
is_last: position === totalItems - 1,
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '🍞 Breadcrumb Clicked', {
|
||||||
|
itemName,
|
||||||
|
position,
|
||||||
|
totalItems,
|
||||||
|
});
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪Logo点击(返回首页)
|
||||||
|
*/
|
||||||
|
const trackLogoClicked = useCallback(() => {
|
||||||
|
track('Logo Clicked', {
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '🏠 Logo Clicked');
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪用户菜单展开
|
||||||
|
* @param {Object} user - 用户对象
|
||||||
|
* @param {number} menuItemCount - 菜单项数量
|
||||||
|
*/
|
||||||
|
const trackUserMenuOpened = useCallback((user = {}, menuItemCount = 0) => {
|
||||||
|
track('User Menu Opened', {
|
||||||
|
user_id: user.id || null,
|
||||||
|
menu_item_count: menuItemCount,
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '👤 User Menu Opened', {
|
||||||
|
userId: user.id,
|
||||||
|
menuItemCount,
|
||||||
|
});
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪通知中心打开
|
||||||
|
* @param {number} unreadCount - 未读通知数量
|
||||||
|
*/
|
||||||
|
const trackNotificationCenterOpened = useCallback((unreadCount = 0) => {
|
||||||
|
track('Notification Center Opened', {
|
||||||
|
unread_count: unreadCount,
|
||||||
|
has_unread: unreadCount > 0,
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '🔔 Notification Center Opened', {
|
||||||
|
unreadCount,
|
||||||
|
});
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪语言切换
|
||||||
|
* @param {string} fromLanguage - 原语言
|
||||||
|
* @param {string} toLanguage - 目标语言
|
||||||
|
*/
|
||||||
|
const trackLanguageChanged = useCallback((fromLanguage, toLanguage) => {
|
||||||
|
if (!fromLanguage || !toLanguage) {
|
||||||
|
logger.warn('useNavigationEvents', 'trackLanguageChanged: both languages are required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Language Changed', {
|
||||||
|
from_language: fromLanguage,
|
||||||
|
to_language: toLanguage,
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '🌐 Language Changed', {
|
||||||
|
fromLanguage,
|
||||||
|
toLanguage,
|
||||||
|
});
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪主题切换(深色/浅色模式)
|
||||||
|
* @param {string} fromTheme - 原主题
|
||||||
|
* @param {string} toTheme - 目标主题
|
||||||
|
*/
|
||||||
|
const trackThemeChanged = useCallback((fromTheme, toTheme) => {
|
||||||
|
if (!fromTheme || !toTheme) {
|
||||||
|
logger.warn('useNavigationEvents', 'trackThemeChanged: both themes are required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Theme Changed', {
|
||||||
|
from_theme: fromTheme,
|
||||||
|
to_theme: toTheme,
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '🎨 Theme Changed', {
|
||||||
|
fromTheme,
|
||||||
|
toTheme,
|
||||||
|
});
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪快捷键使用
|
||||||
|
* @param {string} shortcut - 快捷键组合 (如 'Ctrl+K', 'Cmd+/')
|
||||||
|
* @param {string} action - 触发的动作
|
||||||
|
*/
|
||||||
|
const trackShortcutUsed = useCallback((shortcut, action = '') => {
|
||||||
|
if (!shortcut) {
|
||||||
|
logger.warn('useNavigationEvents', 'trackShortcutUsed: shortcut is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Keyboard Shortcut Used', {
|
||||||
|
shortcut,
|
||||||
|
action,
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '⌨️ Keyboard Shortcut Used', {
|
||||||
|
shortcut,
|
||||||
|
action,
|
||||||
|
});
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪返回按钮点击
|
||||||
|
* @param {string} fromPage - 当前页面
|
||||||
|
* @param {string} toPage - 返回到的页面
|
||||||
|
*/
|
||||||
|
const trackBackButtonClicked = useCallback((fromPage = '', toPage = '') => {
|
||||||
|
track('Back Button Clicked', {
|
||||||
|
from_page: fromPage,
|
||||||
|
to_page: toPage,
|
||||||
|
component,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useNavigationEvents', '◀️ Back Button Clicked', {
|
||||||
|
fromPage,
|
||||||
|
toPage,
|
||||||
|
});
|
||||||
|
}, [track, component]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 导航点击事件
|
||||||
|
trackTopNavClicked,
|
||||||
|
trackSidebarMenuClicked,
|
||||||
|
trackMenuItemClicked,
|
||||||
|
trackBreadcrumbClicked,
|
||||||
|
trackLogoClicked,
|
||||||
|
|
||||||
|
// 用户交互事件
|
||||||
|
trackUserMenuOpened,
|
||||||
|
trackNotificationCenterOpened,
|
||||||
|
|
||||||
|
// 设置变更事件
|
||||||
|
trackLanguageChanged,
|
||||||
|
trackThemeChanged,
|
||||||
|
|
||||||
|
// 其他交互
|
||||||
|
trackShortcutUsed,
|
||||||
|
trackBackButtonClicked,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useNavigationEvents;
|
||||||
334
src/hooks/useProfileEvents.js
Normal file
334
src/hooks/useProfileEvents.js
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
// src/hooks/useProfileEvents.js
|
||||||
|
// 个人资料和设置事件追踪 Hook
|
||||||
|
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { usePostHogTrack } from './usePostHogRedux';
|
||||||
|
import { RETENTION_EVENTS } from '../lib/constants';
|
||||||
|
import { logger } from '../utils/logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个人资料和设置事件追踪 Hook
|
||||||
|
* @param {Object} options - 配置选项
|
||||||
|
* @param {string} options.pageType - 页面类型 ('profile' | 'settings' | 'security')
|
||||||
|
* @returns {Object} 事件追踪处理函数集合
|
||||||
|
*/
|
||||||
|
export const useProfileEvents = ({ pageType = 'profile' } = {}) => {
|
||||||
|
const { track } = usePostHogTrack();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪个人资料字段编辑开始
|
||||||
|
* @param {string} fieldName - 字段名称 ('nickname' | 'email' | 'phone' | 'avatar' | 'bio')
|
||||||
|
*/
|
||||||
|
const trackProfileFieldEditStarted = useCallback((fieldName) => {
|
||||||
|
if (!fieldName) {
|
||||||
|
logger.warn('useProfileEvents', 'trackProfileFieldEditStarted: fieldName is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Profile Field Edit Started', {
|
||||||
|
field_name: fieldName,
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', '✏️ Profile Field Edit Started', {
|
||||||
|
fieldName,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪个人资料更新成功
|
||||||
|
* @param {Array<string>} updatedFields - 更新的字段列表
|
||||||
|
* @param {Object} changes - 变更详情
|
||||||
|
*/
|
||||||
|
const trackProfileUpdated = useCallback((updatedFields = [], changes = {}) => {
|
||||||
|
if (!updatedFields || updatedFields.length === 0) {
|
||||||
|
logger.warn('useProfileEvents', 'trackProfileUpdated: updatedFields array is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(RETENTION_EVENTS.PROFILE_UPDATED, {
|
||||||
|
updated_fields: updatedFields,
|
||||||
|
field_count: updatedFields.length,
|
||||||
|
changes: changes,
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', '✅ Profile Updated', {
|
||||||
|
updatedFields,
|
||||||
|
fieldCount: updatedFields.length,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪个人资料更新失败
|
||||||
|
* @param {Array<string>} attemptedFields - 尝试更新的字段
|
||||||
|
* @param {string} errorMessage - 错误信息
|
||||||
|
*/
|
||||||
|
const trackProfileUpdateFailed = useCallback((attemptedFields = [], errorMessage = '') => {
|
||||||
|
track('Profile Update Failed', {
|
||||||
|
attempted_fields: attemptedFields,
|
||||||
|
error_message: errorMessage,
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', '❌ Profile Update Failed', {
|
||||||
|
attemptedFields,
|
||||||
|
errorMessage,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪头像上传
|
||||||
|
* @param {string} uploadMethod - 上传方式 ('file_upload' | 'url' | 'camera' | 'default_avatar')
|
||||||
|
* @param {number} fileSize - 文件大小(bytes)
|
||||||
|
*/
|
||||||
|
const trackAvatarUploaded = useCallback((uploadMethod = 'file_upload', fileSize = 0) => {
|
||||||
|
track('Avatar Uploaded', {
|
||||||
|
upload_method: uploadMethod,
|
||||||
|
file_size: fileSize,
|
||||||
|
file_size_mb: (fileSize / (1024 * 1024)).toFixed(2),
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', '🖼️ Avatar Uploaded', {
|
||||||
|
uploadMethod,
|
||||||
|
fileSize,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪密码更改
|
||||||
|
* @param {boolean} success - 是否成功
|
||||||
|
* @param {string} errorReason - 失败原因
|
||||||
|
*/
|
||||||
|
const trackPasswordChanged = useCallback((success = true, errorReason = '') => {
|
||||||
|
track('Password Changed', {
|
||||||
|
success,
|
||||||
|
error_reason: errorReason || null,
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', success ? '🔒 Password Changed Successfully' : '❌ Password Change Failed', {
|
||||||
|
success,
|
||||||
|
errorReason,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪邮箱验证发起
|
||||||
|
* @param {string} email - 邮箱地址
|
||||||
|
*/
|
||||||
|
const trackEmailVerificationSent = useCallback((email = '') => {
|
||||||
|
track('Email Verification Sent', {
|
||||||
|
email_provided: Boolean(email),
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', '📧 Email Verification Sent', {
|
||||||
|
emailProvided: Boolean(email),
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪手机号验证发起
|
||||||
|
* @param {string} phone - 手机号
|
||||||
|
*/
|
||||||
|
const trackPhoneVerificationSent = useCallback((phone = '') => {
|
||||||
|
track('Phone Verification Sent', {
|
||||||
|
phone_provided: Boolean(phone),
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', '📱 Phone Verification Sent', {
|
||||||
|
phoneProvided: Boolean(phone),
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪账号绑定(微信、邮箱、手机等)
|
||||||
|
* @param {string} accountType - 账号类型 ('wechat' | 'email' | 'phone')
|
||||||
|
* @param {boolean} success - 是否成功
|
||||||
|
*/
|
||||||
|
const trackAccountBound = useCallback((accountType, success = true) => {
|
||||||
|
if (!accountType) {
|
||||||
|
logger.warn('useProfileEvents', 'trackAccountBound: accountType is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Account Bound', {
|
||||||
|
account_type: accountType,
|
||||||
|
success,
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', success ? '🔗 Account Bound' : '❌ Account Bind Failed', {
|
||||||
|
accountType,
|
||||||
|
success,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪账号解绑
|
||||||
|
* @param {string} accountType - 账号类型
|
||||||
|
* @param {boolean} success - 是否成功
|
||||||
|
*/
|
||||||
|
const trackAccountUnbound = useCallback((accountType, success = true) => {
|
||||||
|
if (!accountType) {
|
||||||
|
logger.warn('useProfileEvents', 'trackAccountUnbound: accountType is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Account Unbound', {
|
||||||
|
account_type: accountType,
|
||||||
|
success,
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', success ? '🔓 Account Unbound' : '❌ Account Unbind Failed', {
|
||||||
|
accountType,
|
||||||
|
success,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪设置项更改
|
||||||
|
* @param {string} settingName - 设置名称
|
||||||
|
* @param {any} oldValue - 旧值
|
||||||
|
* @param {any} newValue - 新值
|
||||||
|
* @param {string} category - 设置分类 ('notification' | 'privacy' | 'display' | 'advanced')
|
||||||
|
*/
|
||||||
|
const trackSettingChanged = useCallback((settingName, oldValue, newValue, category = 'general') => {
|
||||||
|
if (!settingName) {
|
||||||
|
logger.warn('useProfileEvents', 'trackSettingChanged: settingName is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(RETENTION_EVENTS.SETTINGS_CHANGED, {
|
||||||
|
setting_name: settingName,
|
||||||
|
old_value: String(oldValue),
|
||||||
|
new_value: String(newValue),
|
||||||
|
category,
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', '⚙️ Setting Changed', {
|
||||||
|
settingName,
|
||||||
|
oldValue,
|
||||||
|
newValue,
|
||||||
|
category,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪通知偏好更改
|
||||||
|
* @param {Object} preferences - 通知偏好设置
|
||||||
|
* @param {boolean} preferences.email - 邮件通知
|
||||||
|
* @param {boolean} preferences.push - 推送通知
|
||||||
|
* @param {boolean} preferences.sms - 短信通知
|
||||||
|
*/
|
||||||
|
const trackNotificationPreferencesChanged = useCallback((preferences = {}) => {
|
||||||
|
track('Notification Preferences Changed', {
|
||||||
|
email_enabled: preferences.email || false,
|
||||||
|
push_enabled: preferences.push || false,
|
||||||
|
sms_enabled: preferences.sms || false,
|
||||||
|
total_enabled: Object.values(preferences).filter(Boolean).length,
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', '🔔 Notification Preferences Changed', {
|
||||||
|
preferences,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪隐私设置更改
|
||||||
|
* @param {string} privacySetting - 隐私设置名称
|
||||||
|
* @param {boolean} isPublic - 是否公开
|
||||||
|
*/
|
||||||
|
const trackPrivacySettingChanged = useCallback((privacySetting, isPublic = false) => {
|
||||||
|
if (!privacySetting) {
|
||||||
|
logger.warn('useProfileEvents', 'trackPrivacySettingChanged: privacySetting is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Privacy Setting Changed', {
|
||||||
|
privacy_setting: privacySetting,
|
||||||
|
is_public: isPublic,
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', '🔐 Privacy Setting Changed', {
|
||||||
|
privacySetting,
|
||||||
|
isPublic,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪账号删除请求
|
||||||
|
* @param {string} reason - 删除原因
|
||||||
|
*/
|
||||||
|
const trackAccountDeletionRequested = useCallback((reason = '') => {
|
||||||
|
track('Account Deletion Requested', {
|
||||||
|
reason,
|
||||||
|
has_reason: Boolean(reason),
|
||||||
|
page_type: pageType,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useProfileEvents', '🗑️ Account Deletion Requested', {
|
||||||
|
reason,
|
||||||
|
pageType,
|
||||||
|
});
|
||||||
|
}, [track, pageType]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 个人资料编辑
|
||||||
|
trackProfileFieldEditStarted,
|
||||||
|
trackProfileUpdated,
|
||||||
|
trackProfileUpdateFailed,
|
||||||
|
trackAvatarUploaded,
|
||||||
|
|
||||||
|
// 安全和验证
|
||||||
|
trackPasswordChanged,
|
||||||
|
trackEmailVerificationSent,
|
||||||
|
trackPhoneVerificationSent,
|
||||||
|
|
||||||
|
// 账号绑定
|
||||||
|
trackAccountBound,
|
||||||
|
trackAccountUnbound,
|
||||||
|
|
||||||
|
// 设置更改
|
||||||
|
trackSettingChanged,
|
||||||
|
trackNotificationPreferencesChanged,
|
||||||
|
trackPrivacySettingChanged,
|
||||||
|
|
||||||
|
// 账号管理
|
||||||
|
trackAccountDeletionRequested,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useProfileEvents;
|
||||||
244
src/hooks/useSearchEvents.js
Normal file
244
src/hooks/useSearchEvents.js
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
// src/hooks/useSearchEvents.js
|
||||||
|
// 全局搜索功能事件追踪 Hook
|
||||||
|
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { usePostHogTrack } from './usePostHogRedux';
|
||||||
|
import { RETENTION_EVENTS } from '../lib/constants';
|
||||||
|
import { logger } from '../utils/logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局搜索事件追踪 Hook
|
||||||
|
* @param {Object} options - 配置选项
|
||||||
|
* @param {string} options.context - 搜索上下文 ('global' | 'stock' | 'news' | 'concept' | 'simulation')
|
||||||
|
* @returns {Object} 事件追踪处理函数集合
|
||||||
|
*/
|
||||||
|
export const useSearchEvents = ({ context = 'global' } = {}) => {
|
||||||
|
const { track } = usePostHogTrack();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪搜索开始(聚焦搜索框)
|
||||||
|
* @param {string} placeholder - 搜索框提示文本
|
||||||
|
*/
|
||||||
|
const trackSearchInitiated = useCallback((placeholder = '') => {
|
||||||
|
track(RETENTION_EVENTS.SEARCH_INITIATED, {
|
||||||
|
context,
|
||||||
|
placeholder,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSearchEvents', '🔍 Search Initiated', {
|
||||||
|
context,
|
||||||
|
placeholder,
|
||||||
|
});
|
||||||
|
}, [track, context]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪搜索查询提交
|
||||||
|
* @param {string} query - 搜索查询词
|
||||||
|
* @param {number} resultCount - 搜索结果数量
|
||||||
|
* @param {Object} filters - 应用的筛选条件
|
||||||
|
*/
|
||||||
|
const trackSearchQuerySubmitted = useCallback((query, resultCount = 0, filters = {}) => {
|
||||||
|
if (!query) {
|
||||||
|
logger.warn('useSearchEvents', 'trackSearchQuerySubmitted: query is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(RETENTION_EVENTS.SEARCH_QUERY_SUBMITTED, {
|
||||||
|
query,
|
||||||
|
query_length: query.length,
|
||||||
|
result_count: resultCount,
|
||||||
|
has_results: resultCount > 0,
|
||||||
|
context,
|
||||||
|
filters: filters,
|
||||||
|
filter_count: Object.keys(filters).length,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果没有搜索结果,额外追踪
|
||||||
|
if (resultCount === 0) {
|
||||||
|
track(RETENTION_EVENTS.SEARCH_NO_RESULTS, {
|
||||||
|
query,
|
||||||
|
context,
|
||||||
|
filters,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSearchEvents', '❌ Search No Results', {
|
||||||
|
query,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.debug('useSearchEvents', '✅ Search Query Submitted', {
|
||||||
|
query,
|
||||||
|
resultCount,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [track, context]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪搜索结果点击
|
||||||
|
* @param {Object} result - 被点击的搜索结果
|
||||||
|
* @param {string} result.type - 结果类型 ('stock' | 'news' | 'concept' | 'event')
|
||||||
|
* @param {string} result.id - 结果ID
|
||||||
|
* @param {string} result.title - 结果标题
|
||||||
|
* @param {number} position - 在搜索结果中的位置
|
||||||
|
* @param {string} query - 搜索查询词
|
||||||
|
*/
|
||||||
|
const trackSearchResultClicked = useCallback((result, position = 0, query = '') => {
|
||||||
|
if (!result || !result.type) {
|
||||||
|
logger.warn('useSearchEvents', 'trackSearchResultClicked: result object with type is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(RETENTION_EVENTS.SEARCH_RESULT_CLICKED, {
|
||||||
|
result_type: result.type,
|
||||||
|
result_id: result.id || result.code || '',
|
||||||
|
result_title: result.title || result.name || '',
|
||||||
|
position,
|
||||||
|
query,
|
||||||
|
context,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSearchEvents', '🎯 Search Result Clicked', {
|
||||||
|
type: result.type,
|
||||||
|
id: result.id || result.code,
|
||||||
|
position,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
}, [track, context]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪搜索筛选应用
|
||||||
|
* @param {Object} filters - 应用的筛选条件
|
||||||
|
* @param {string} filterType - 筛选类型 ('sort' | 'category' | 'date_range' | 'price_range')
|
||||||
|
* @param {any} filterValue - 筛选值
|
||||||
|
*/
|
||||||
|
const trackSearchFilterApplied = useCallback((filterType, filterValue, filters = {}) => {
|
||||||
|
if (!filterType) {
|
||||||
|
logger.warn('useSearchEvents', 'trackSearchFilterApplied: filterType is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(RETENTION_EVENTS.SEARCH_FILTER_APPLIED, {
|
||||||
|
filter_type: filterType,
|
||||||
|
filter_value: String(filterValue),
|
||||||
|
all_filters: filters,
|
||||||
|
context,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSearchEvents', '🔍 Search Filter Applied', {
|
||||||
|
filterType,
|
||||||
|
filterValue,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
}, [track, context]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪搜索建议点击(自动完成)
|
||||||
|
* @param {string} suggestion - 被点击的搜索建议
|
||||||
|
* @param {number} position - 在建议列表中的位置
|
||||||
|
* @param {string} source - 建议来源 ('history' | 'popular' | 'related')
|
||||||
|
*/
|
||||||
|
const trackSearchSuggestionClicked = useCallback((suggestion, position = 0, source = 'popular') => {
|
||||||
|
if (!suggestion) {
|
||||||
|
logger.warn('useSearchEvents', 'trackSearchSuggestionClicked: suggestion is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Search Suggestion Clicked', {
|
||||||
|
suggestion,
|
||||||
|
position,
|
||||||
|
source,
|
||||||
|
context,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSearchEvents', '💡 Search Suggestion Clicked', {
|
||||||
|
suggestion,
|
||||||
|
position,
|
||||||
|
source,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
}, [track, context]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪搜索历史查看
|
||||||
|
* @param {number} historyCount - 历史记录数量
|
||||||
|
*/
|
||||||
|
const trackSearchHistoryViewed = useCallback((historyCount = 0) => {
|
||||||
|
track('Search History Viewed', {
|
||||||
|
history_count: historyCount,
|
||||||
|
has_history: historyCount > 0,
|
||||||
|
context,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSearchEvents', '📜 Search History Viewed', {
|
||||||
|
historyCount,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
}, [track, context]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪搜索历史清除
|
||||||
|
*/
|
||||||
|
const trackSearchHistoryCleared = useCallback(() => {
|
||||||
|
track('Search History Cleared', {
|
||||||
|
context,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSearchEvents', '🗑️ Search History Cleared', {
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
}, [track, context]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪热门搜索词点击
|
||||||
|
* @param {string} keyword - 被点击的热门关键词
|
||||||
|
* @param {number} position - 在列表中的位置
|
||||||
|
* @param {number} heatScore - 热度分数
|
||||||
|
*/
|
||||||
|
const trackPopularKeywordClicked = useCallback((keyword, position = 0, heatScore = 0) => {
|
||||||
|
if (!keyword) {
|
||||||
|
logger.warn('useSearchEvents', 'trackPopularKeywordClicked: keyword is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Popular Keyword Clicked', {
|
||||||
|
keyword,
|
||||||
|
position,
|
||||||
|
heat_score: heatScore,
|
||||||
|
context,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSearchEvents', '🔥 Popular Keyword Clicked', {
|
||||||
|
keyword,
|
||||||
|
position,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
}, [track, context]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 搜索流程事件
|
||||||
|
trackSearchInitiated,
|
||||||
|
trackSearchQuerySubmitted,
|
||||||
|
trackSearchResultClicked,
|
||||||
|
|
||||||
|
// 筛选和建议
|
||||||
|
trackSearchFilterApplied,
|
||||||
|
trackSearchSuggestionClicked,
|
||||||
|
|
||||||
|
// 历史和热门
|
||||||
|
trackSearchHistoryViewed,
|
||||||
|
trackSearchHistoryCleared,
|
||||||
|
trackPopularKeywordClicked,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSearchEvents;
|
||||||
394
src/hooks/useSubscriptionEvents.js
Normal file
394
src/hooks/useSubscriptionEvents.js
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
// src/hooks/useSubscriptionEvents.js
|
||||||
|
// 订阅和支付事件追踪 Hook
|
||||||
|
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { usePostHogTrack } from './usePostHogRedux';
|
||||||
|
import { RETENTION_EVENTS, REVENUE_EVENTS } from '../lib/constants';
|
||||||
|
import { logger } from '../utils/logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅和支付事件追踪 Hook
|
||||||
|
* @param {Object} options - 配置选项
|
||||||
|
* @param {Object} options.currentSubscription - 当前订阅信息
|
||||||
|
* @returns {Object} 事件追踪处理函数集合
|
||||||
|
*/
|
||||||
|
export const useSubscriptionEvents = ({ currentSubscription = null } = {}) => {
|
||||||
|
const { track } = usePostHogTrack();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪付费墙展示
|
||||||
|
* @param {string} feature - 被限制的功能名称
|
||||||
|
* @param {string} requiredPlan - 需要的订阅计划
|
||||||
|
* @param {string} triggerLocation - 触发位置
|
||||||
|
*/
|
||||||
|
const trackPaywallShown = useCallback((feature, requiredPlan = 'pro', triggerLocation = '') => {
|
||||||
|
if (!feature) {
|
||||||
|
logger.warn('useSubscriptionEvents', 'trackPaywallShown: feature is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(REVENUE_EVENTS.PAYWALL_SHOWN, {
|
||||||
|
feature,
|
||||||
|
required_plan: requiredPlan,
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
trigger_location: triggerLocation,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '🚧 Paywall Shown', {
|
||||||
|
feature,
|
||||||
|
requiredPlan,
|
||||||
|
triggerLocation,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪付费墙关闭
|
||||||
|
* @param {string} feature - 功能名称
|
||||||
|
* @param {string} closeMethod - 关闭方式 ('dismiss' | 'upgrade_clicked' | 'back_button')
|
||||||
|
*/
|
||||||
|
const trackPaywallDismissed = useCallback((feature, closeMethod = 'dismiss') => {
|
||||||
|
if (!feature) {
|
||||||
|
logger.warn('useSubscriptionEvents', 'trackPaywallDismissed: feature is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(REVENUE_EVENTS.PAYWALL_DISMISSED, {
|
||||||
|
feature,
|
||||||
|
close_method: closeMethod,
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '❌ Paywall Dismissed', {
|
||||||
|
feature,
|
||||||
|
closeMethod,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪升级按钮点击
|
||||||
|
* @param {string} targetPlan - 目标订阅计划
|
||||||
|
* @param {string} source - 来源位置
|
||||||
|
* @param {string} feature - 关联的功能(如果从付费墙点击)
|
||||||
|
*/
|
||||||
|
const trackUpgradePlanClicked = useCallback((targetPlan = 'pro', source = '', feature = '') => {
|
||||||
|
track(REVENUE_EVENTS.PAYWALL_UPGRADE_CLICKED, {
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
target_plan: targetPlan,
|
||||||
|
source,
|
||||||
|
feature: feature || null,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '⬆️ Upgrade Plan Clicked', {
|
||||||
|
currentPlan: currentSubscription?.plan,
|
||||||
|
targetPlan,
|
||||||
|
source,
|
||||||
|
feature,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪订阅页面查看
|
||||||
|
* @param {string} source - 来源
|
||||||
|
*/
|
||||||
|
const trackSubscriptionPageViewed = useCallback((source = '') => {
|
||||||
|
track(RETENTION_EVENTS.SUBSCRIPTION_PAGE_VIEWED, {
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
subscription_status: currentSubscription?.status || 'unknown',
|
||||||
|
is_paid_user: currentSubscription?.plan && currentSubscription.plan !== 'free',
|
||||||
|
source,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '💳 Subscription Page Viewed', {
|
||||||
|
currentPlan: currentSubscription?.plan,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪定价计划查看
|
||||||
|
* @param {string} planName - 计划名称 ('free' | 'pro' | 'enterprise')
|
||||||
|
* @param {number} price - 价格
|
||||||
|
*/
|
||||||
|
const trackPricingPlanViewed = useCallback((planName, price = 0) => {
|
||||||
|
if (!planName) {
|
||||||
|
logger.warn('useSubscriptionEvents', 'trackPricingPlanViewed: planName is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Pricing Plan Viewed', {
|
||||||
|
plan_name: planName,
|
||||||
|
price,
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '👀 Pricing Plan Viewed', {
|
||||||
|
planName,
|
||||||
|
price,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪定价计划选择
|
||||||
|
* @param {string} planName - 选择的计划名称
|
||||||
|
* @param {string} billingCycle - 计费周期 ('monthly' | 'yearly')
|
||||||
|
* @param {number} price - 价格
|
||||||
|
*/
|
||||||
|
const trackPricingPlanSelected = useCallback((planName, billingCycle = 'monthly', price = 0) => {
|
||||||
|
if (!planName) {
|
||||||
|
logger.warn('useSubscriptionEvents', 'trackPricingPlanSelected: planName is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Pricing Plan Selected', {
|
||||||
|
plan_name: planName,
|
||||||
|
billing_cycle: billingCycle,
|
||||||
|
price,
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '✅ Pricing Plan Selected', {
|
||||||
|
planName,
|
||||||
|
billingCycle,
|
||||||
|
price,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪支付页面查看
|
||||||
|
* @param {string} planName - 购买的计划
|
||||||
|
* @param {number} amount - 支付金额
|
||||||
|
*/
|
||||||
|
const trackPaymentPageViewed = useCallback((planName, amount = 0) => {
|
||||||
|
track(REVENUE_EVENTS.PAYMENT_PAGE_VIEWED, {
|
||||||
|
plan_name: planName,
|
||||||
|
amount,
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '💰 Payment Page Viewed', {
|
||||||
|
planName,
|
||||||
|
amount,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪支付方式选择
|
||||||
|
* @param {string} paymentMethod - 支付方式 ('wechat_pay' | 'alipay' | 'credit_card')
|
||||||
|
* @param {number} amount - 支付金额
|
||||||
|
*/
|
||||||
|
const trackPaymentMethodSelected = useCallback((paymentMethod, amount = 0) => {
|
||||||
|
if (!paymentMethod) {
|
||||||
|
logger.warn('useSubscriptionEvents', 'trackPaymentMethodSelected: paymentMethod is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track(REVENUE_EVENTS.PAYMENT_METHOD_SELECTED, {
|
||||||
|
payment_method: paymentMethod,
|
||||||
|
amount,
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '💳 Payment Method Selected', {
|
||||||
|
paymentMethod,
|
||||||
|
amount,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪支付发起
|
||||||
|
* @param {Object} paymentInfo - 支付信息
|
||||||
|
* @param {string} paymentInfo.planName - 计划名称
|
||||||
|
* @param {string} paymentInfo.paymentMethod - 支付方式
|
||||||
|
* @param {number} paymentInfo.amount - 金额
|
||||||
|
* @param {string} paymentInfo.billingCycle - 计费周期
|
||||||
|
* @param {string} paymentInfo.orderId - 订单ID
|
||||||
|
*/
|
||||||
|
const trackPaymentInitiated = useCallback((paymentInfo = {}) => {
|
||||||
|
track(REVENUE_EVENTS.PAYMENT_INITIATED, {
|
||||||
|
plan_name: paymentInfo.planName,
|
||||||
|
payment_method: paymentInfo.paymentMethod,
|
||||||
|
amount: paymentInfo.amount,
|
||||||
|
billing_cycle: paymentInfo.billingCycle,
|
||||||
|
order_id: paymentInfo.orderId,
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '🚀 Payment Initiated', {
|
||||||
|
planName: paymentInfo.planName,
|
||||||
|
amount: paymentInfo.amount,
|
||||||
|
paymentMethod: paymentInfo.paymentMethod,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪支付成功
|
||||||
|
* @param {Object} paymentInfo - 支付信息
|
||||||
|
*/
|
||||||
|
const trackPaymentSuccessful = useCallback((paymentInfo = {}) => {
|
||||||
|
track(REVENUE_EVENTS.PAYMENT_SUCCESSFUL, {
|
||||||
|
plan_name: paymentInfo.planName,
|
||||||
|
payment_method: paymentInfo.paymentMethod,
|
||||||
|
amount: paymentInfo.amount,
|
||||||
|
billing_cycle: paymentInfo.billingCycle,
|
||||||
|
order_id: paymentInfo.orderId,
|
||||||
|
transaction_id: paymentInfo.transactionId,
|
||||||
|
previous_plan: currentSubscription?.plan || 'free',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '✅ Payment Successful', {
|
||||||
|
planName: paymentInfo.planName,
|
||||||
|
amount: paymentInfo.amount,
|
||||||
|
orderId: paymentInfo.orderId,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪支付失败
|
||||||
|
* @param {Object} paymentInfo - 支付信息
|
||||||
|
* @param {string} errorReason - 失败原因
|
||||||
|
*/
|
||||||
|
const trackPaymentFailed = useCallback((paymentInfo = {}, errorReason = '') => {
|
||||||
|
track(REVENUE_EVENTS.PAYMENT_FAILED, {
|
||||||
|
plan_name: paymentInfo.planName,
|
||||||
|
payment_method: paymentInfo.paymentMethod,
|
||||||
|
amount: paymentInfo.amount,
|
||||||
|
error_reason: errorReason,
|
||||||
|
order_id: paymentInfo.orderId,
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '❌ Payment Failed', {
|
||||||
|
planName: paymentInfo.planName,
|
||||||
|
errorReason,
|
||||||
|
orderId: paymentInfo.orderId,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪订阅创建成功
|
||||||
|
* @param {Object} subscription - 订阅信息
|
||||||
|
*/
|
||||||
|
const trackSubscriptionCreated = useCallback((subscription = {}) => {
|
||||||
|
track(REVENUE_EVENTS.SUBSCRIPTION_CREATED, {
|
||||||
|
plan_name: subscription.plan,
|
||||||
|
billing_cycle: subscription.billingCycle,
|
||||||
|
amount: subscription.amount,
|
||||||
|
start_date: subscription.startDate,
|
||||||
|
end_date: subscription.endDate,
|
||||||
|
previous_plan: currentSubscription?.plan || 'free',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '🎉 Subscription Created', {
|
||||||
|
plan: subscription.plan,
|
||||||
|
billingCycle: subscription.billingCycle,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪订阅续费
|
||||||
|
* @param {Object} subscription - 订阅信息
|
||||||
|
*/
|
||||||
|
const trackSubscriptionRenewed = useCallback((subscription = {}) => {
|
||||||
|
track(REVENUE_EVENTS.SUBSCRIPTION_RENEWED, {
|
||||||
|
plan_name: subscription.plan,
|
||||||
|
amount: subscription.amount,
|
||||||
|
previous_end_date: subscription.previousEndDate,
|
||||||
|
new_end_date: subscription.newEndDate,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '🔄 Subscription Renewed', {
|
||||||
|
plan: subscription.plan,
|
||||||
|
amount: subscription.amount,
|
||||||
|
});
|
||||||
|
}, [track]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪订阅取消
|
||||||
|
* @param {string} reason - 取消原因
|
||||||
|
* @param {boolean} cancelImmediately - 是否立即取消
|
||||||
|
*/
|
||||||
|
const trackSubscriptionCancelled = useCallback((reason = '', cancelImmediately = false) => {
|
||||||
|
track(REVENUE_EVENTS.SUBSCRIPTION_CANCELLED, {
|
||||||
|
plan_name: currentSubscription?.plan,
|
||||||
|
reason,
|
||||||
|
has_reason: Boolean(reason),
|
||||||
|
cancel_immediately: cancelImmediately,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', '🚫 Subscription Cancelled', {
|
||||||
|
plan: currentSubscription?.plan,
|
||||||
|
reason,
|
||||||
|
cancelImmediately,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪优惠券应用
|
||||||
|
* @param {string} couponCode - 优惠券代码
|
||||||
|
* @param {number} discountAmount - 折扣金额
|
||||||
|
* @param {boolean} success - 是否成功
|
||||||
|
*/
|
||||||
|
const trackCouponApplied = useCallback((couponCode, discountAmount = 0, success = true) => {
|
||||||
|
if (!couponCode) {
|
||||||
|
logger.warn('useSubscriptionEvents', 'trackCouponApplied: couponCode is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
track('Coupon Applied', {
|
||||||
|
coupon_code: couponCode,
|
||||||
|
discount_amount: discountAmount,
|
||||||
|
success,
|
||||||
|
current_plan: currentSubscription?.plan || 'free',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug('useSubscriptionEvents', success ? '🎟️ Coupon Applied' : '❌ Coupon Failed', {
|
||||||
|
couponCode,
|
||||||
|
discountAmount,
|
||||||
|
success,
|
||||||
|
});
|
||||||
|
}, [track, currentSubscription]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 付费墙事件
|
||||||
|
trackPaywallShown,
|
||||||
|
trackPaywallDismissed,
|
||||||
|
trackUpgradePlanClicked,
|
||||||
|
|
||||||
|
// 订阅页面事件
|
||||||
|
trackSubscriptionPageViewed,
|
||||||
|
trackPricingPlanViewed,
|
||||||
|
trackPricingPlanSelected,
|
||||||
|
|
||||||
|
// 支付流程事件
|
||||||
|
trackPaymentPageViewed,
|
||||||
|
trackPaymentMethodSelected,
|
||||||
|
trackPaymentInitiated,
|
||||||
|
trackPaymentSuccessful,
|
||||||
|
trackPaymentFailed,
|
||||||
|
|
||||||
|
// 订阅管理事件
|
||||||
|
trackSubscriptionCreated,
|
||||||
|
trackSubscriptionRenewed,
|
||||||
|
trackSubscriptionCancelled,
|
||||||
|
|
||||||
|
// 优惠券事件
|
||||||
|
trackCouponApplied,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSubscriptionEvents;
|
||||||
Reference in New Issue
Block a user