// src/hooks/useAuthEvents.js // 认证事件追踪 Hook import { useCallback } from 'react'; import { usePostHogTrack, usePostHogUser } from './usePostHogRedux'; import { ACTIVATION_EVENTS } from '../lib/constants'; import { logger } from '../utils/logger'; /** * 认证事件追踪 Hook * 提供登录/注册流程中所有关键节点的事件追踪功能 * * 用法示例: * * ```jsx * import { useAuthEvents } from 'hooks/useAuthEvents'; * * function AuthComponent() { * const { * trackLoginPageViewed, * trackPhoneLoginInitiated, * trackVerificationCodeSent, * trackLoginSuccess * } = useAuthEvents(); * * useEffect(() => { * trackLoginPageViewed(); * }, [trackLoginPageViewed]); * * const handlePhoneFocus = () => { * trackPhoneLoginInitiated(formData.phone); * }; * } * ``` * * @param {Object} options - 配置选项 * @param {string} options.component - 组件名称 ('AuthFormContent' | 'WechatRegister') * @param {boolean} options.isMobile - 是否为移动设备 * @returns {Object} 事件追踪处理函数集合 */ export const useAuthEvents = ({ component = 'AuthFormContent', isMobile = false } = {}) => { const { track } = usePostHogTrack(); const { identify } = usePostHogUser(); // 通用事件属性 const getBaseProperties = useCallback(() => ({ component, device: isMobile ? 'mobile' : 'desktop', timestamp: new Date().toISOString(), }), [component, isMobile]); // ==================== 页面浏览事件 ==================== /** * 追踪登录页面浏览 */ const trackLoginPageViewed = useCallback(() => { track(ACTIVATION_EVENTS.LOGIN_PAGE_VIEWED, getBaseProperties()); logger.debug('useAuthEvents', '📄 Login Page Viewed', { component }); }, [track, getBaseProperties, component]); // ==================== 登录方式选择 ==================== /** * 追踪用户开始手机号登录 * @param {string} phone - 手机号(可选,用于判断是否已填写) */ const trackPhoneLoginInitiated = useCallback((phone = '') => { track(ACTIVATION_EVENTS.PHONE_LOGIN_INITIATED, { ...getBaseProperties(), has_phone: Boolean(phone), }); logger.debug('useAuthEvents', '📱 Phone Login Initiated', { hasPhone: Boolean(phone) }); }, [track, getBaseProperties]); /** * 追踪用户选择微信登录 * @param {string} source - 触发来源 ('qr_area' | 'icon_button' | 'h5_redirect') */ const trackWechatLoginInitiated = useCallback((source = 'qr_area') => { track(ACTIVATION_EVENTS.WECHAT_LOGIN_INITIATED, { ...getBaseProperties(), source, }); logger.debug('useAuthEvents', '💬 WeChat Login Initiated', { source }); }, [track, getBaseProperties]); // ==================== 手机验证码流程 ==================== /** * 追踪验证码发送成功 * @param {string} phone - 手机号 * @param {string} purpose - 发送目的 ('login' | 'register') */ const trackVerificationCodeSent = useCallback((phone, purpose = 'login') => { track(ACTIVATION_EVENTS.VERIFICATION_CODE_SENT, { ...getBaseProperties(), phone_masked: phone ? phone.substring(0, 3) + '****' + phone.substring(7) : '', purpose, }); logger.debug('useAuthEvents', '✉️ Verification Code Sent', { phone: phone?.substring(0, 3) + '****', purpose }); }, [track, getBaseProperties]); /** * 追踪验证码发送失败 * @param {string} phone - 手机号 * @param {Error|string} error - 错误对象或错误消息 */ const trackVerificationCodeSendFailed = useCallback((phone, error) => { const errorMessage = typeof error === 'string' ? error : error?.message || 'Unknown error'; track(ACTIVATION_EVENTS.VERIFICATION_CODE_SEND_FAILED, { ...getBaseProperties(), phone_masked: phone ? phone.substring(0, 3) + '****' + phone.substring(7) : '', error_message: errorMessage, error_type: 'send_code_failed', }); logger.debug('useAuthEvents', '❌ Verification Code Send Failed', { error: errorMessage }); }, [track, getBaseProperties]); /** * 追踪用户输入验证码 * @param {number} codeLength - 当前输入的验证码长度 */ const trackVerificationCodeInputChanged = useCallback((codeLength) => { track(ACTIVATION_EVENTS.VERIFICATION_CODE_INPUT_CHANGED, { ...getBaseProperties(), code_length: codeLength, is_complete: codeLength >= 6, }); logger.debug('useAuthEvents', '⌨️ Verification Code Input Changed', { codeLength }); }, [track, getBaseProperties]); /** * 追踪重新发送验证码 * @param {string} phone - 手机号 * @param {number} attemptCount - 第几次重发(可选) */ const trackVerificationCodeResent = useCallback((phone, attemptCount = 1) => { track(ACTIVATION_EVENTS.VERIFICATION_CODE_RESENT, { ...getBaseProperties(), phone_masked: phone ? phone.substring(0, 3) + '****' + phone.substring(7) : '', attempt_count: attemptCount, }); logger.debug('useAuthEvents', '🔄 Verification Code Resent', { attempt: attemptCount }); }, [track, getBaseProperties]); /** * 追踪手机号验证结果 * @param {string} phone - 手机号 * @param {boolean} isValid - 是否有效 * @param {string} errorType - 错误类型(可选) */ const trackPhoneNumberValidated = useCallback((phone, isValid, errorType = '') => { track(ACTIVATION_EVENTS.PHONE_NUMBER_VALIDATED, { ...getBaseProperties(), phone_masked: phone ? phone.substring(0, 3) + '****' + phone.substring(7) : '', is_valid: isValid, error_type: errorType, }); logger.debug('useAuthEvents', '✓ Phone Number Validated', { isValid, errorType }); }, [track, getBaseProperties]); /** * 追踪验证码提交 * @param {string} phone - 手机号 */ const trackVerificationCodeSubmitted = useCallback((phone) => { track(ACTIVATION_EVENTS.VERIFICATION_CODE_SUBMITTED, { ...getBaseProperties(), phone_masked: phone ? phone.substring(0, 3) + '****' + phone.substring(7) : '', }); logger.debug('useAuthEvents', '📤 Verification Code Submitted'); }, [track, getBaseProperties]); // ==================== 微信登录流程 ==================== /** * 追踪微信二维码显示 * @param {string} sessionId - 会话ID * @param {string} authUrl - 授权URL */ const trackWechatQRDisplayed = useCallback((sessionId, authUrl = '') => { track(ACTIVATION_EVENTS.WECHAT_QR_DISPLAYED, { ...getBaseProperties(), session_id: sessionId?.substring(0, 8) + '...', has_auth_url: Boolean(authUrl), }); logger.debug('useAuthEvents', '🔲 WeChat QR Code Displayed', { sessionId: sessionId?.substring(0, 8) }); }, [track, getBaseProperties]); /** * 追踪微信二维码被扫描 * @param {string} sessionId - 会话ID */ const trackWechatQRScanned = useCallback((sessionId) => { track(ACTIVATION_EVENTS.WECHAT_QR_SCANNED, { ...getBaseProperties(), session_id: sessionId?.substring(0, 8) + '...', }); logger.debug('useAuthEvents', '📱 WeChat QR Code Scanned', { sessionId: sessionId?.substring(0, 8) }); }, [track, getBaseProperties]); /** * 追踪微信二维码过期 * @param {string} sessionId - 会话ID * @param {number} timeElapsed - 经过时间(秒) */ const trackWechatQRExpired = useCallback((sessionId, timeElapsed = 0) => { track(ACTIVATION_EVENTS.WECHAT_QR_EXPIRED, { ...getBaseProperties(), session_id: sessionId?.substring(0, 8) + '...', time_elapsed: timeElapsed, }); logger.debug('useAuthEvents', '⏰ WeChat QR Code Expired', { sessionId: sessionId?.substring(0, 8), timeElapsed }); }, [track, getBaseProperties]); /** * 追踪刷新微信二维码 * @param {string} oldSessionId - 旧会话ID * @param {string} newSessionId - 新会话ID */ const trackWechatQRRefreshed = useCallback((oldSessionId, newSessionId) => { track(ACTIVATION_EVENTS.WECHAT_QR_REFRESHED, { ...getBaseProperties(), old_session_id: oldSessionId?.substring(0, 8) + '...', new_session_id: newSessionId?.substring(0, 8) + '...', }); logger.debug('useAuthEvents', '🔄 WeChat QR Code Refreshed'); }, [track, getBaseProperties]); /** * 追踪微信登录状态变化 * @param {string} sessionId - 会话ID * @param {string} oldStatus - 旧状态 * @param {string} newStatus - 新状态 */ const trackWechatStatusChanged = useCallback((sessionId, oldStatus, newStatus) => { track(ACTIVATION_EVENTS.WECHAT_STATUS_CHANGED, { ...getBaseProperties(), session_id: sessionId?.substring(0, 8) + '...', old_status: oldStatus, new_status: newStatus, }); logger.debug('useAuthEvents', '🔄 WeChat Status Changed', { oldStatus, newStatus }); }, [track, getBaseProperties]); /** * 追踪移动端跳转微信H5授权 */ const trackWechatH5Redirect = useCallback(() => { track(ACTIVATION_EVENTS.WECHAT_H5_REDIRECT, getBaseProperties()); logger.debug('useAuthEvents', '🔗 WeChat H5 Redirect'); }, [track, getBaseProperties]); // ==================== 登录/注册结果 ==================== /** * 追踪登录成功并识别用户 * @param {Object} user - 用户对象 * @param {string} loginMethod - 登录方式 ('wechat' | 'phone') * @param {boolean} isNewUser - 是否为新注册用户 */ const trackLoginSuccess = useCallback((user, loginMethod, isNewUser = false) => { // 追踪登录成功事件 const eventName = isNewUser ? ACTIVATION_EVENTS.USER_SIGNED_UP : ACTIVATION_EVENTS.USER_LOGGED_IN; track(eventName, { ...getBaseProperties(), user_id: user.id, login_method: loginMethod, is_new_user: isNewUser, has_nickname: Boolean(user.nickname), has_email: Boolean(user.email), has_wechat: Boolean(user.wechat_open_id), }); // 识别用户(关联 PostHog 用户) identify(user.id.toString(), { phone: user.phone, username: user.username, nickname: user.nickname, email: user.email, login_method: loginMethod, is_new_user: isNewUser, registration_date: user.created_at, last_login: new Date().toISOString(), has_wechat: Boolean(user.wechat_open_id), wechat_open_id: user.wechat_open_id, wechat_union_id: user.wechat_union_id, }); logger.debug('useAuthEvents', `✅ ${isNewUser ? 'User Signed Up' : 'User Logged In'}`, { userId: user.id, method: loginMethod, isNewUser, }); }, [track, identify, getBaseProperties]); /** * 追踪登录失败 * @param {string} loginMethod - 登录方式 ('wechat' | 'phone') * @param {string} errorType - 错误类型 * @param {string} errorMessage - 错误消息 * @param {Object} context - 额外上下文信息 */ const trackLoginFailed = useCallback((loginMethod, errorType, errorMessage, context = {}) => { track(ACTIVATION_EVENTS.LOGIN_FAILED, { ...getBaseProperties(), login_method: loginMethod, error_type: errorType, error_message: errorMessage, ...context, }); logger.debug('useAuthEvents', '❌ Login Failed', { method: loginMethod, errorType, errorMessage }); }, [track, getBaseProperties]); // ==================== 用户行为细节 ==================== /** * 追踪表单字段聚焦 * @param {string} fieldName - 字段名称 ('phone' | 'verificationCode') */ const trackFormFocused = useCallback((fieldName) => { track(ACTIVATION_EVENTS.AUTH_FORM_FOCUSED, { ...getBaseProperties(), field_name: fieldName, }); logger.debug('useAuthEvents', '🎯 Form Field Focused', { fieldName }); }, [track, getBaseProperties]); /** * 追踪表单验证错误 * @param {string} fieldName - 字段名称 * @param {string} errorType - 错误类型 * @param {string} errorMessage - 错误消息 */ const trackFormValidationError = useCallback((fieldName, errorType, errorMessage) => { track(ACTIVATION_EVENTS.AUTH_FORM_VALIDATION_ERROR, { ...getBaseProperties(), field_name: fieldName, error_type: errorType, error_message: errorMessage, }); logger.debug('useAuthEvents', '⚠️ Form Validation Error', { fieldName, errorType }); }, [track, getBaseProperties]); /** * 追踪昵称设置引导弹窗显示 * @param {string} phone - 手机号 */ const trackNicknamePromptShown = useCallback((phone) => { track(ACTIVATION_EVENTS.NICKNAME_PROMPT_SHOWN, { ...getBaseProperties(), phone_masked: phone ? phone.substring(0, 3) + '****' + phone.substring(7) : '', }); logger.debug('useAuthEvents', '💬 Nickname Prompt Shown'); }, [track, getBaseProperties]); /** * 追踪用户接受设置昵称 */ const trackNicknamePromptAccepted = useCallback(() => { track(ACTIVATION_EVENTS.NICKNAME_PROMPT_ACCEPTED, getBaseProperties()); logger.debug('useAuthEvents', '✅ Nickname Prompt Accepted'); }, [track, getBaseProperties]); /** * 追踪用户跳过设置昵称 */ const trackNicknamePromptSkipped = useCallback(() => { track(ACTIVATION_EVENTS.NICKNAME_PROMPT_SKIPPED, getBaseProperties()); logger.debug('useAuthEvents', '⏭️ Nickname Prompt Skipped'); }, [track, getBaseProperties]); /** * 追踪用户点击用户协议链接 */ const trackUserAgreementClicked = useCallback(() => { track(ACTIVATION_EVENTS.USER_AGREEMENT_LINK_CLICKED, getBaseProperties()); logger.debug('useAuthEvents', '📄 User Agreement Link Clicked'); }, [track, getBaseProperties]); /** * 追踪用户点击隐私政策链接 */ const trackPrivacyPolicyClicked = useCallback(() => { track(ACTIVATION_EVENTS.PRIVACY_POLICY_LINK_CLICKED, getBaseProperties()); logger.debug('useAuthEvents', '📄 Privacy Policy Link Clicked'); }, [track, getBaseProperties]); // ==================== 错误追踪 ==================== /** * 追踪通用错误 * @param {string} errorType - 错误类型 ('network' | 'api' | 'validation' | 'session') * @param {string} errorMessage - 错误消息 * @param {Object} context - 错误上下文 */ const trackError = useCallback((errorType, errorMessage, context = {}) => { const eventMap = { network: ACTIVATION_EVENTS.NETWORK_ERROR_OCCURRED, api: ACTIVATION_EVENTS.API_ERROR_OCCURRED, session: ACTIVATION_EVENTS.SESSION_EXPIRED, default: ACTIVATION_EVENTS.LOGIN_ERROR_OCCURRED, }; const eventName = eventMap[errorType] || eventMap.default; track(eventName, { ...getBaseProperties(), error_type: errorType, error_message: errorMessage, ...context, }); logger.error('useAuthEvents', `❌ ${errorType} Error`, { errorMessage, context }); }, [track, getBaseProperties]); // ==================== 返回接口 ==================== return { // 页面浏览 trackLoginPageViewed, // 登录方式选择 trackPhoneLoginInitiated, trackWechatLoginInitiated, // 手机验证码流程 trackVerificationCodeSent, trackVerificationCodeSendFailed, trackVerificationCodeInputChanged, trackVerificationCodeResent, trackPhoneNumberValidated, trackVerificationCodeSubmitted, // 微信登录流程 trackWechatQRDisplayed, trackWechatQRScanned, trackWechatQRExpired, trackWechatQRRefreshed, trackWechatStatusChanged, trackWechatH5Redirect, // 登录/注册结果 trackLoginSuccess, trackLoginFailed, // 用户行为 trackFormFocused, trackFormValidationError, trackNicknamePromptShown, trackNicknamePromptAccepted, trackNicknamePromptSkipped, trackUserAgreementClicked, trackPrivacyPolicyClicked, // 错误追踪 trackError, }; }; export default useAuthEvents;