feat: PostHog 集成\
1. ✅ 安装依赖: posthog-js@^1.280.1 2. ✅ 创建核心文件: - src/lib/posthog.js - PostHog SDK 封装(271 行) - src/lib/constants.js - 事件常量定义(AARRR 框架) - src/hooks/usePostHog.js - PostHog React Hook - src/hooks/usePageTracking.js - 页面追踪 Hook - src/components/PostHogProvider.js - Provider 组件 3. ✅ 集成到应用: - 修改 src/App.js,在最外层添加 <PostHogProvider> - 自动追踪所有页面浏览 4. ✅ 配置环境变量: - 在 .env 添加 PostHog 配置项 - REACT_APP_POSTHOG_KEY 留空,需要用户填写 5. ✅ 创建文档: POSTHOG_INTEGRATION.md 包含完整的使用说明
This commit is contained in:
@@ -43,6 +43,7 @@
|
||||
"match-sorter": "6.3.0",
|
||||
"moment": "^2.29.1",
|
||||
"nouislider": "15.0.0",
|
||||
"posthog-js": "^1.281.0",
|
||||
"react": "18.3.1",
|
||||
"react-apexcharts": "^1.3.9",
|
||||
"react-big-calendar": "^0.33.2",
|
||||
|
||||
57
src/App.js
57
src/App.js
@@ -59,6 +59,7 @@ import NotificationContainer from "components/NotificationContainer";
|
||||
import ConnectionStatusBar from "components/ConnectionStatusBar";
|
||||
import NotificationTestTool from "components/NotificationTestTool";
|
||||
import ScrollToTop from "components/ScrollToTop";
|
||||
import PostHogProvider from "components/PostHogProvider";
|
||||
import { logger } from "utils/logger";
|
||||
|
||||
/**
|
||||
@@ -295,32 +296,34 @@ export default function App() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ReduxProvider store={store}>
|
||||
<ChakraProvider
|
||||
theme={theme}
|
||||
toastOptions={{
|
||||
defaultOptions: {
|
||||
position: 'top',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<NotificationProvider>
|
||||
<AuthProvider>
|
||||
<AuthModalProvider>
|
||||
<IndustryProvider>
|
||||
<AppContent />
|
||||
<AuthModalManager />
|
||||
<NotificationContainer />
|
||||
<NotificationTestTool />
|
||||
</IndustryProvider>
|
||||
</AuthModalProvider>
|
||||
</AuthProvider>
|
||||
</NotificationProvider>
|
||||
</ErrorBoundary>
|
||||
</ChakraProvider>
|
||||
</ReduxProvider>
|
||||
<PostHogProvider>
|
||||
<ReduxProvider store={store}>
|
||||
<ChakraProvider
|
||||
theme={theme}
|
||||
toastOptions={{
|
||||
defaultOptions: {
|
||||
position: 'top',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<NotificationProvider>
|
||||
<AuthProvider>
|
||||
<AuthModalProvider>
|
||||
<IndustryProvider>
|
||||
<AppContent />
|
||||
<AuthModalManager />
|
||||
<NotificationContainer />
|
||||
<NotificationTestTool />
|
||||
</IndustryProvider>
|
||||
</AuthModalProvider>
|
||||
</AuthProvider>
|
||||
</NotificationProvider>
|
||||
</ErrorBoundary>
|
||||
</ChakraProvider>
|
||||
</ReduxProvider>
|
||||
</PostHogProvider>
|
||||
);
|
||||
}
|
||||
83
src/components/PostHogProvider.js
Normal file
83
src/components/PostHogProvider.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// src/components/PostHogProvider.js
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { initPostHog } from '../lib/posthog';
|
||||
import { usePageTracking } from '../hooks/usePageTracking';
|
||||
|
||||
/**
|
||||
* PostHog Provider Component
|
||||
* Initializes PostHog SDK and provides automatic page view tracking
|
||||
*
|
||||
* Usage:
|
||||
* <PostHogProvider>
|
||||
* <App />
|
||||
* </PostHogProvider>
|
||||
*/
|
||||
export const PostHogProvider = ({ children }) => {
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
// Initialize PostHog once when component mounts
|
||||
useEffect(() => {
|
||||
// Only run in browser
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
// Initialize PostHog
|
||||
initPostHog();
|
||||
setIsInitialized(true);
|
||||
|
||||
// Log initialization
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('✅ PostHogProvider initialized');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Automatically track page views
|
||||
usePageTracking({
|
||||
enabled: isInitialized,
|
||||
getProperties: (location) => {
|
||||
// Add custom properties based on route
|
||||
const properties = {};
|
||||
|
||||
// Identify page type based on path
|
||||
if (location.pathname === '/home' || location.pathname === '/home/') {
|
||||
properties.page_type = 'landing';
|
||||
} else if (location.pathname.startsWith('/home/center')) {
|
||||
properties.page_type = 'dashboard';
|
||||
} else if (location.pathname.startsWith('/auth/')) {
|
||||
properties.page_type = 'auth';
|
||||
} else if (location.pathname.startsWith('/community')) {
|
||||
properties.page_type = 'feature';
|
||||
properties.feature_name = 'community';
|
||||
} else if (location.pathname.startsWith('/concepts')) {
|
||||
properties.page_type = 'feature';
|
||||
properties.feature_name = 'concepts';
|
||||
} else if (location.pathname.startsWith('/stocks')) {
|
||||
properties.page_type = 'feature';
|
||||
properties.feature_name = 'stocks';
|
||||
} else if (location.pathname.startsWith('/limit-analyse')) {
|
||||
properties.page_type = 'feature';
|
||||
properties.feature_name = 'limit_analyse';
|
||||
} else if (location.pathname.startsWith('/trading-simulation')) {
|
||||
properties.page_type = 'feature';
|
||||
properties.feature_name = 'trading_simulation';
|
||||
} else if (location.pathname.startsWith('/company')) {
|
||||
properties.page_type = 'detail';
|
||||
properties.content_type = 'company';
|
||||
} else if (location.pathname.startsWith('/event-detail')) {
|
||||
properties.page_type = 'detail';
|
||||
properties.content_type = 'event';
|
||||
}
|
||||
|
||||
return properties;
|
||||
},
|
||||
});
|
||||
|
||||
// Don't render children until PostHog is initialized
|
||||
// This prevents tracking events before SDK is ready
|
||||
if (!isInitialized) {
|
||||
return children; // Or return a loading spinner
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default PostHogProvider;
|
||||
55
src/hooks/usePageTracking.js
Normal file
55
src/hooks/usePageTracking.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// src/hooks/usePageTracking.js
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import posthog from 'posthog-js';
|
||||
|
||||
/**
|
||||
* Custom hook for automatic page view tracking with PostHog
|
||||
*
|
||||
* @param {Object} options - Configuration options
|
||||
* @param {boolean} options.enabled - Whether tracking is enabled
|
||||
* @param {Function} options.getProperties - Function to get custom properties for each page view
|
||||
*/
|
||||
export const usePageTracking = ({ enabled = true, getProperties } = {}) => {
|
||||
const location = useLocation();
|
||||
const previousPathRef = useRef('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
|
||||
// Get the current path
|
||||
const currentPath = location.pathname + location.search;
|
||||
|
||||
// Skip if it's the same page (prevents duplicate tracking)
|
||||
if (previousPathRef.current === currentPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the previous path
|
||||
previousPathRef.current = currentPath;
|
||||
|
||||
// Get custom properties if function provided
|
||||
const customProperties = getProperties ? getProperties(location) : {};
|
||||
|
||||
// Track page view with PostHog
|
||||
if (posthog && posthog.__loaded) {
|
||||
posthog.capture('$pageview', {
|
||||
$current_url: window.location.href,
|
||||
path: location.pathname,
|
||||
search: location.search,
|
||||
hash: location.hash,
|
||||
...customProperties,
|
||||
});
|
||||
|
||||
// Log in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('📊 PostHog $pageview:', {
|
||||
path: location.pathname,
|
||||
...customProperties,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [location, enabled, getProperties]);
|
||||
};
|
||||
|
||||
export default usePageTracking;
|
||||
101
src/hooks/usePostHog.js
Normal file
101
src/hooks/usePostHog.js
Normal file
@@ -0,0 +1,101 @@
|
||||
// src/hooks/usePostHog.js
|
||||
import { useCallback } from 'react';
|
||||
import {
|
||||
getPostHog,
|
||||
trackEvent,
|
||||
trackPageView,
|
||||
identifyUser,
|
||||
setUserProperties,
|
||||
resetUser,
|
||||
optIn,
|
||||
optOut,
|
||||
hasOptedOut,
|
||||
getFeatureFlag,
|
||||
isFeatureEnabled,
|
||||
} from '../lib/posthog';
|
||||
|
||||
/**
|
||||
* Custom hook to access PostHog functionality
|
||||
* Provides convenient methods for tracking events and managing user sessions
|
||||
*
|
||||
* @returns {object} PostHog methods
|
||||
*/
|
||||
export const usePostHog = () => {
|
||||
// Get PostHog instance
|
||||
const posthog = getPostHog();
|
||||
|
||||
// Track custom event
|
||||
const track = useCallback((eventName, properties = {}) => {
|
||||
trackEvent(eventName, properties);
|
||||
}, []);
|
||||
|
||||
// Track page view
|
||||
const trackPage = useCallback((pagePath, properties = {}) => {
|
||||
trackPageView(pagePath, properties);
|
||||
}, []);
|
||||
|
||||
// Identify user
|
||||
const identify = useCallback((userId, userProperties = {}) => {
|
||||
identifyUser(userId, userProperties);
|
||||
}, []);
|
||||
|
||||
// Set user properties
|
||||
const setProperties = useCallback((properties) => {
|
||||
setUserProperties(properties);
|
||||
}, []);
|
||||
|
||||
// Reset user session (logout)
|
||||
const reset = useCallback(() => {
|
||||
resetUser();
|
||||
}, []);
|
||||
|
||||
// Opt out of tracking
|
||||
const optOutTracking = useCallback(() => {
|
||||
optOut();
|
||||
}, []);
|
||||
|
||||
// Opt in to tracking
|
||||
const optInTracking = useCallback(() => {
|
||||
optIn();
|
||||
}, []);
|
||||
|
||||
// Check if user has opted out
|
||||
const isOptedOut = useCallback(() => {
|
||||
return hasOptedOut();
|
||||
}, []);
|
||||
|
||||
// Get feature flag value
|
||||
const getFlag = useCallback((flagKey, defaultValue = false) => {
|
||||
return getFeatureFlag(flagKey, defaultValue);
|
||||
}, []);
|
||||
|
||||
// Check if feature is enabled
|
||||
const isEnabled = useCallback((flagKey) => {
|
||||
return isFeatureEnabled(flagKey);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
// Core PostHog instance
|
||||
posthog,
|
||||
|
||||
// Tracking methods
|
||||
track,
|
||||
trackPage,
|
||||
|
||||
// User management
|
||||
identify,
|
||||
setProperties,
|
||||
reset,
|
||||
|
||||
// Privacy controls
|
||||
optOut: optOutTracking,
|
||||
optIn: optInTracking,
|
||||
isOptedOut,
|
||||
|
||||
// Feature flags
|
||||
getFlag,
|
||||
isEnabled,
|
||||
};
|
||||
};
|
||||
|
||||
export default usePostHog;
|
||||
351
src/lib/constants.js
Normal file
351
src/lib/constants.js
Normal file
@@ -0,0 +1,351 @@
|
||||
// src/lib/constants.js
|
||||
// PostHog Event Names and Constants
|
||||
// Organized by AARRR Framework (Acquisition, Activation, Retention, Referral, Revenue)
|
||||
|
||||
// ============================================================================
|
||||
// ACQUISITION (获客) - Landing page, marketing website events
|
||||
// ============================================================================
|
||||
export const ACQUISITION_EVENTS = {
|
||||
// Landing page
|
||||
LANDING_PAGE_VIEWED: 'Landing Page Viewed',
|
||||
CTA_BUTTON_CLICKED: 'CTA Button Clicked',
|
||||
FEATURE_CARD_VIEWED: 'Feature Card Viewed',
|
||||
FEATURE_VIDEO_PLAYED: 'Feature Video Played',
|
||||
|
||||
// Pricing page
|
||||
PRICING_PAGE_VIEWED: 'Pricing Page Viewed',
|
||||
PRICING_PLAN_VIEWED: 'Pricing Plan Viewed',
|
||||
PRICING_PLAN_SELECTED: 'Pricing Plan Selected',
|
||||
|
||||
// How to use page
|
||||
HOW_TO_USE_PAGE_VIEWED: 'How To Use Page Viewed',
|
||||
TUTORIAL_STEP_VIEWED: 'Tutorial Step Viewed',
|
||||
|
||||
// Roadmap page
|
||||
ROADMAP_PAGE_VIEWED: 'Roadmap Page Viewed',
|
||||
ROADMAP_ITEM_CLICKED: 'Roadmap Item Clicked',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ACTIVATION (激活) - Sign up, login, onboarding
|
||||
// ============================================================================
|
||||
export const ACTIVATION_EVENTS = {
|
||||
// Auth pages
|
||||
LOGIN_PAGE_VIEWED: 'Login Page Viewed',
|
||||
SIGNUP_PAGE_VIEWED: 'Signup Page Viewed',
|
||||
|
||||
// Login/Signup actions
|
||||
LOGIN_METHOD_SELECTED: 'Login Method Selected', // wechat, email, phone
|
||||
WECHAT_QR_DISPLAYED: 'WeChat QR Code Displayed',
|
||||
WECHAT_QR_SCANNED: 'WeChat QR Code Scanned',
|
||||
USER_LOGGED_IN: 'User Logged In',
|
||||
USER_SIGNED_UP: 'User Signed Up',
|
||||
VERIFICATION_CODE_SENT: 'Verification Code Sent',
|
||||
VERIFICATION_CODE_SUBMITTED: 'Verification Code Submitted',
|
||||
LOGIN_FAILED: 'Login Failed',
|
||||
SIGNUP_FAILED: 'Signup Failed',
|
||||
|
||||
// Onboarding
|
||||
ONBOARDING_STARTED: 'Onboarding Started',
|
||||
ONBOARDING_STEP_COMPLETED: 'Onboarding Step Completed',
|
||||
ONBOARDING_COMPLETED: 'Onboarding Completed',
|
||||
ONBOARDING_SKIPPED: 'Onboarding Skipped',
|
||||
|
||||
// User agreement
|
||||
USER_AGREEMENT_VIEWED: 'User Agreement Viewed',
|
||||
USER_AGREEMENT_ACCEPTED: 'User Agreement Accepted',
|
||||
PRIVACY_POLICY_VIEWED: 'Privacy Policy Viewed',
|
||||
PRIVACY_POLICY_ACCEPTED: 'Privacy Policy Accepted',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// RETENTION (留存) - Core product usage, feature engagement
|
||||
// ============================================================================
|
||||
export const RETENTION_EVENTS = {
|
||||
// Dashboard
|
||||
DASHBOARD_VIEWED: 'Dashboard Viewed',
|
||||
DASHBOARD_CENTER_VIEWED: 'Dashboard Center Viewed',
|
||||
FUNCTION_CARD_CLICKED: 'Function Card Clicked', // Core功能卡片点击
|
||||
|
||||
// Navigation
|
||||
TOP_NAV_CLICKED: 'Top Navigation Clicked',
|
||||
SIDEBAR_MENU_CLICKED: 'Sidebar Menu Clicked',
|
||||
MENU_ITEM_CLICKED: 'Menu Item Clicked',
|
||||
BREADCRUMB_CLICKED: 'Breadcrumb Clicked',
|
||||
|
||||
// Search
|
||||
SEARCH_INITIATED: 'Search Initiated',
|
||||
SEARCH_QUERY_SUBMITTED: 'Search Query Submitted',
|
||||
SEARCH_RESULT_CLICKED: 'Search Result Clicked',
|
||||
SEARCH_NO_RESULTS: 'Search No Results',
|
||||
SEARCH_FILTER_APPLIED: 'Search Filter Applied',
|
||||
|
||||
// News/Community (新闻催化分析)
|
||||
COMMUNITY_PAGE_VIEWED: 'Community Page Viewed',
|
||||
NEWS_LIST_VIEWED: 'News List Viewed',
|
||||
NEWS_ARTICLE_CLICKED: 'News Article Clicked',
|
||||
NEWS_DETAIL_OPENED: 'News Detail Opened',
|
||||
NEWS_TAB_CLICKED: 'News Tab Clicked', // 相关标的, 相关概念, etc.
|
||||
NEWS_FILTER_APPLIED: 'News Filter Applied',
|
||||
NEWS_SORTED: 'News Sorted',
|
||||
|
||||
// Concept Center (概念中心)
|
||||
CONCEPT_PAGE_VIEWED: 'Concept Page Viewed',
|
||||
CONCEPT_LIST_VIEWED: 'Concept List Viewed',
|
||||
CONCEPT_CLICKED: 'Concept Clicked',
|
||||
CONCEPT_DETAIL_VIEWED: 'Concept Detail Viewed',
|
||||
CONCEPT_STOCK_CLICKED: 'Concept Stock Clicked',
|
||||
|
||||
// Stock Center (个股中心)
|
||||
STOCK_OVERVIEW_VIEWED: 'Stock Overview Page Viewed',
|
||||
STOCK_LIST_VIEWED: 'Stock List Viewed',
|
||||
STOCK_SEARCHED: 'Stock Searched',
|
||||
STOCK_CLICKED: 'Stock Clicked',
|
||||
STOCK_DETAIL_VIEWED: 'Stock Detail Viewed',
|
||||
STOCK_TAB_CLICKED: 'Stock Tab Clicked', // 公司概览, 股票行情, 财务全景, 盈利预测
|
||||
|
||||
// Company Details
|
||||
COMPANY_OVERVIEW_VIEWED: 'Company Overview Viewed',
|
||||
COMPANY_FINANCIALS_VIEWED: 'Company Financials Viewed',
|
||||
COMPANY_FORECAST_VIEWED: 'Company Forecast Viewed',
|
||||
COMPANY_MARKET_DATA_VIEWED: 'Company Market Data Viewed',
|
||||
|
||||
// Limit Analysis (涨停分析)
|
||||
LIMIT_ANALYSE_PAGE_VIEWED: 'Limit Analyse Page Viewed',
|
||||
LIMIT_BOARD_CLICKED: 'Limit Board Clicked',
|
||||
LIMIT_SECTOR_EXPANDED: 'Limit Sector Expanded',
|
||||
LIMIT_SECTOR_ANALYSIS_VIEWED: 'Limit Sector Analysis Viewed',
|
||||
LIMIT_STOCK_CLICKED: 'Limit Stock Clicked',
|
||||
|
||||
// Trading Simulation (模拟盘交易)
|
||||
TRADING_SIMULATION_ENTERED: 'Trading Simulation Entered',
|
||||
SIMULATION_ORDER_PLACED: 'Simulation Order Placed',
|
||||
SIMULATION_HOLDINGS_VIEWED: 'Simulation Holdings Viewed',
|
||||
SIMULATION_HISTORY_VIEWED: 'Simulation History Viewed',
|
||||
SIMULATION_STOCK_SEARCHED: 'Simulation Stock Searched',
|
||||
|
||||
// Event Details
|
||||
EVENT_DETAIL_VIEWED: 'Event Detail Viewed',
|
||||
EVENT_ANALYSIS_VIEWED: 'Event Analysis Viewed',
|
||||
EVENT_TIMELINE_CLICKED: 'Event Timeline Clicked',
|
||||
|
||||
// Profile & Settings
|
||||
PROFILE_PAGE_VIEWED: 'Profile Page Viewed',
|
||||
PROFILE_UPDATED: 'Profile Updated',
|
||||
SETTINGS_PAGE_VIEWED: 'Settings Page Viewed',
|
||||
SETTINGS_CHANGED: 'Settings Changed',
|
||||
|
||||
// Subscription Management
|
||||
SUBSCRIPTION_PAGE_VIEWED: 'Subscription Page Viewed',
|
||||
UPGRADE_PLAN_CLICKED: 'Upgrade Plan Clicked',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// REFERRAL (推荐) - Sharing, inviting
|
||||
// ============================================================================
|
||||
export const REFERRAL_EVENTS = {
|
||||
// Sharing
|
||||
SHARE_BUTTON_CLICKED: 'Share Button Clicked',
|
||||
CONTENT_SHARED: 'Content Shared',
|
||||
SHARE_LINK_GENERATED: 'Share Link Generated',
|
||||
SHARE_MODAL_OPENED: 'Share Modal Opened',
|
||||
SHARE_MODAL_CLOSED: 'Share Modal Closed',
|
||||
|
||||
// Referral
|
||||
REFERRAL_PAGE_VIEWED: 'Referral Page Viewed',
|
||||
REFERRAL_LINK_COPIED: 'Referral Link Copied',
|
||||
REFERRAL_INVITE_SENT: 'Referral Invite Sent',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// REVENUE (收入) - Payment, subscription, monetization
|
||||
// ============================================================================
|
||||
export const REVENUE_EVENTS = {
|
||||
// Paywall
|
||||
PAYWALL_SHOWN: 'Paywall Shown',
|
||||
PAYWALL_DISMISSED: 'Paywall Dismissed',
|
||||
PAYWALL_UPGRADE_CLICKED: 'Paywall Upgrade Clicked',
|
||||
|
||||
// Payment
|
||||
PAYMENT_PAGE_VIEWED: 'Payment Page Viewed',
|
||||
PAYMENT_METHOD_SELECTED: 'Payment Method Selected',
|
||||
PAYMENT_INITIATED: 'Payment Initiated',
|
||||
PAYMENT_SUCCESSFUL: 'Payment Successful',
|
||||
PAYMENT_FAILED: 'Payment Failed',
|
||||
|
||||
// Subscription
|
||||
SUBSCRIPTION_CREATED: 'Subscription Created',
|
||||
SUBSCRIPTION_RENEWED: 'Subscription Renewed',
|
||||
SUBSCRIPTION_UPGRADED: 'Subscription Upgraded',
|
||||
SUBSCRIPTION_DOWNGRADED: 'Subscription Downgraded',
|
||||
SUBSCRIPTION_CANCELLED: 'Subscription Cancelled',
|
||||
SUBSCRIPTION_EXPIRED: 'Subscription Expired',
|
||||
|
||||
// Refund
|
||||
REFUND_REQUESTED: 'Refund Requested',
|
||||
REFUND_PROCESSED: 'Refund Processed',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SPECIAL EVENTS (特殊事件) - Errors, performance, chatbot
|
||||
// ============================================================================
|
||||
export const SPECIAL_EVENTS = {
|
||||
// Errors
|
||||
ERROR_OCCURRED: 'Error Occurred',
|
||||
API_ERROR: 'API Error',
|
||||
NOT_FOUND_404: '404 Not Found',
|
||||
|
||||
// Performance
|
||||
PAGE_LOAD_TIME: 'Page Load Time',
|
||||
API_RESPONSE_TIME: 'API Response Time',
|
||||
|
||||
// Chatbot (Dify)
|
||||
CHATBOT_OPENED: 'Chatbot Opened',
|
||||
CHATBOT_CLOSED: 'Chatbot Closed',
|
||||
CHATBOT_MESSAGE_SENT: 'Chatbot Message Sent',
|
||||
CHATBOT_MESSAGE_RECEIVED: 'Chatbot Message Received',
|
||||
CHATBOT_FEEDBACK_PROVIDED: 'Chatbot Feedback Provided',
|
||||
|
||||
// Scroll depth
|
||||
SCROLL_DEPTH_25: 'Scroll Depth 25%',
|
||||
SCROLL_DEPTH_50: 'Scroll Depth 50%',
|
||||
SCROLL_DEPTH_75: 'Scroll Depth 75%',
|
||||
SCROLL_DEPTH_100: 'Scroll Depth 100%',
|
||||
|
||||
// Session
|
||||
SESSION_STARTED: 'Session Started',
|
||||
SESSION_ENDED: 'Session Ended',
|
||||
USER_IDLE: 'User Idle',
|
||||
USER_RETURNED: 'User Returned',
|
||||
|
||||
// Logout
|
||||
USER_LOGGED_OUT: 'User Logged Out',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// USER PROPERTIES (用户属性)
|
||||
// ============================================================================
|
||||
export const USER_PROPERTIES = {
|
||||
// Identity
|
||||
EMAIL: 'email',
|
||||
USERNAME: 'username',
|
||||
USER_ID: 'user_id',
|
||||
PHONE: 'phone',
|
||||
|
||||
// Subscription
|
||||
SUBSCRIPTION_TIER: 'subscription_tier', // 'free', 'pro', 'enterprise'
|
||||
SUBSCRIPTION_STATUS: 'subscription_status', // 'active', 'expired', 'cancelled'
|
||||
SUBSCRIPTION_START_DATE: 'subscription_start_date',
|
||||
SUBSCRIPTION_END_DATE: 'subscription_end_date',
|
||||
|
||||
// Engagement
|
||||
REGISTRATION_DATE: 'registration_date',
|
||||
LAST_LOGIN: 'last_login',
|
||||
LOGIN_COUNT: 'login_count',
|
||||
DAYS_SINCE_REGISTRATION: 'days_since_registration',
|
||||
LIFETIME_VALUE: 'lifetime_value',
|
||||
|
||||
// Preferences
|
||||
PREFERRED_LANGUAGE: 'preferred_language',
|
||||
THEME_PREFERENCE: 'theme_preference', // 'light', 'dark'
|
||||
NOTIFICATION_ENABLED: 'notification_enabled',
|
||||
|
||||
// Attribution
|
||||
UTM_SOURCE: 'utm_source',
|
||||
UTM_MEDIUM: 'utm_medium',
|
||||
UTM_CAMPAIGN: 'utm_campaign',
|
||||
REFERRER: 'referrer',
|
||||
|
||||
// Behavioral
|
||||
FAVORITE_FEATURES: 'favorite_features',
|
||||
MOST_VISITED_PAGES: 'most_visited_pages',
|
||||
TOTAL_SESSIONS: 'total_sessions',
|
||||
AVERAGE_SESSION_DURATION: 'average_session_duration',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SUBSCRIPTION TIERS (订阅等级)
|
||||
// ============================================================================
|
||||
export const SUBSCRIPTION_TIERS = {
|
||||
FREE: 'free',
|
||||
PRO: 'pro',
|
||||
ENTERPRISE: 'enterprise',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// PAGE TYPES (页面类型)
|
||||
// ============================================================================
|
||||
export const PAGE_TYPES = {
|
||||
LANDING: 'landing',
|
||||
DASHBOARD: 'dashboard',
|
||||
FEATURE: 'feature',
|
||||
DETAIL: 'detail',
|
||||
AUTH: 'auth',
|
||||
SETTINGS: 'settings',
|
||||
PAYMENT: 'payment',
|
||||
ERROR: 'error',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// CONTENT TYPES (内容类型)
|
||||
// ============================================================================
|
||||
export const CONTENT_TYPES = {
|
||||
NEWS: 'news',
|
||||
STOCK: 'stock',
|
||||
CONCEPT: 'concept',
|
||||
ANALYSIS: 'analysis',
|
||||
EVENT: 'event',
|
||||
COMPANY: 'company',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// SHARE CHANNELS (分享渠道)
|
||||
// ============================================================================
|
||||
export const SHARE_CHANNELS = {
|
||||
WECHAT: 'wechat',
|
||||
LINK: 'link',
|
||||
QRCODE: 'qrcode',
|
||||
EMAIL: 'email',
|
||||
COPY: 'copy',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// LOGIN METHODS (登录方式)
|
||||
// ============================================================================
|
||||
export const LOGIN_METHODS = {
|
||||
WECHAT: 'wechat',
|
||||
EMAIL: 'email',
|
||||
PHONE: 'phone',
|
||||
USERNAME: 'username',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// PAYMENT METHODS (支付方式)
|
||||
// ============================================================================
|
||||
export const PAYMENT_METHODS = {
|
||||
WECHAT_PAY: 'wechat_pay',
|
||||
ALIPAY: 'alipay',
|
||||
CREDIT_CARD: 'credit_card',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Helper function to get all events
|
||||
// ============================================================================
|
||||
export const getAllEvents = () => {
|
||||
return {
|
||||
...ACQUISITION_EVENTS,
|
||||
...ACTIVATION_EVENTS,
|
||||
...RETENTION_EVENTS,
|
||||
...REFERRAL_EVENTS,
|
||||
...REVENUE_EVENTS,
|
||||
...SPECIAL_EVENTS,
|
||||
};
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Helper function to validate event name
|
||||
// ============================================================================
|
||||
export const isValidEvent = (eventName) => {
|
||||
const allEvents = getAllEvents();
|
||||
return Object.values(allEvents).includes(eventName);
|
||||
};
|
||||
271
src/lib/posthog.js
Normal file
271
src/lib/posthog.js
Normal file
@@ -0,0 +1,271 @@
|
||||
// src/lib/posthog.js
|
||||
import posthog from 'posthog-js';
|
||||
|
||||
/**
|
||||
* Initialize PostHog SDK
|
||||
* Should be called once when the app starts
|
||||
*/
|
||||
export const initPostHog = () => {
|
||||
// Only run in browser environment
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const apiKey = process.env.REACT_APP_POSTHOG_KEY;
|
||||
const apiHost = process.env.REACT_APP_POSTHOG_HOST || 'https://app.posthog.com';
|
||||
|
||||
if (!apiKey) {
|
||||
console.warn('⚠️ PostHog API key not found. Analytics will be disabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
posthog.init(apiKey, {
|
||||
api_host: apiHost,
|
||||
|
||||
// Pageview tracking - manual control for better accuracy
|
||||
capture_pageview: false, // We'll manually capture with custom properties
|
||||
capture_pageleave: true, // Auto-capture when user leaves page
|
||||
|
||||
// Session Recording Configuration
|
||||
session_recording: {
|
||||
enabled: process.env.REACT_APP_ENABLE_SESSION_RECORDING === 'true',
|
||||
|
||||
// Privacy: Mask sensitive input fields
|
||||
maskInputOptions: {
|
||||
password: true,
|
||||
email: true,
|
||||
phone: true,
|
||||
'data-sensitive': true, // Custom attribute for sensitive fields
|
||||
},
|
||||
|
||||
// Record canvas for charts/graphs
|
||||
recordCanvas: true,
|
||||
|
||||
// Network payload capture (useful for debugging API issues)
|
||||
networkPayloadCapture: {
|
||||
recordHeaders: true,
|
||||
recordBody: true,
|
||||
// Don't record sensitive endpoints
|
||||
urlBlocklist: [
|
||||
'/api/auth/session',
|
||||
'/api/auth/login',
|
||||
'/api/auth/register',
|
||||
'/api/payment',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// Performance optimization
|
||||
batch_size: 10, // Send events in batches of 10
|
||||
batch_interval_ms: 3000, // Or every 3 seconds
|
||||
|
||||
// Privacy settings
|
||||
respect_dnt: true, // Respect Do Not Track browser setting
|
||||
persistence: 'localStorage+cookie', // Use both for reliability
|
||||
|
||||
// Feature flags (for A/B testing)
|
||||
bootstrap: {
|
||||
featureFlags: {},
|
||||
},
|
||||
|
||||
// Autocapture settings
|
||||
autocapture: {
|
||||
// Automatically capture clicks on buttons, links, etc.
|
||||
dom_event_allowlist: ['click', 'submit', 'change'],
|
||||
|
||||
// Capture additional element properties
|
||||
capture_copied_text: false, // Don't capture copied text (privacy)
|
||||
},
|
||||
|
||||
// Development debugging
|
||||
loaded: (posthogInstance) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('✅ PostHog initialized successfully');
|
||||
posthogInstance.debug(); // Enable debug mode in development
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
console.log('📊 PostHog Analytics initialized');
|
||||
} catch (error) {
|
||||
console.error('❌ PostHog initialization failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get PostHog instance
|
||||
* @returns {object} PostHog instance
|
||||
*/
|
||||
export const getPostHog = () => {
|
||||
return posthog;
|
||||
};
|
||||
|
||||
/**
|
||||
* Identify user with PostHog
|
||||
* Call this after successful login/registration
|
||||
*
|
||||
* @param {string} userId - Unique user identifier
|
||||
* @param {object} userProperties - User properties (email, name, subscription_tier, etc.)
|
||||
*/
|
||||
export const identifyUser = (userId, userProperties = {}) => {
|
||||
if (!userId) {
|
||||
console.warn('⚠️ Cannot identify user: userId is required');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
posthog.identify(userId, {
|
||||
email: userProperties.email,
|
||||
username: userProperties.username,
|
||||
subscription_tier: userProperties.subscription_tier || 'free',
|
||||
role: userProperties.role,
|
||||
registration_date: userProperties.registration_date,
|
||||
last_login: new Date().toISOString(),
|
||||
...userProperties,
|
||||
});
|
||||
|
||||
console.log('👤 User identified:', userId);
|
||||
} catch (error) {
|
||||
console.error('❌ User identification failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user properties
|
||||
* Use this to update user attributes without re-identifying
|
||||
*
|
||||
* @param {object} properties - Properties to update
|
||||
*/
|
||||
export const setUserProperties = (properties) => {
|
||||
try {
|
||||
posthog.people.set(properties);
|
||||
console.log('📝 User properties updated');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to update user properties:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Track custom event
|
||||
*
|
||||
* @param {string} eventName - Name of the event
|
||||
* @param {object} properties - Event properties
|
||||
*/
|
||||
export const trackEvent = (eventName, properties = {}) => {
|
||||
try {
|
||||
posthog.capture(eventName, {
|
||||
...properties,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('📍 Event tracked:', eventName, properties);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Event tracking failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Track page view
|
||||
*
|
||||
* @param {string} pagePath - Current page path
|
||||
* @param {object} properties - Additional properties
|
||||
*/
|
||||
export const trackPageView = (pagePath, properties = {}) => {
|
||||
try {
|
||||
posthog.capture('$pageview', {
|
||||
$current_url: window.location.href,
|
||||
page_path: pagePath,
|
||||
page_title: document.title,
|
||||
referrer: document.referrer,
|
||||
...properties,
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('📄 Page view tracked:', pagePath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Page view tracking failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset user session
|
||||
* Call this on logout
|
||||
*/
|
||||
export const resetUser = () => {
|
||||
try {
|
||||
posthog.reset();
|
||||
console.log('🔄 User session reset');
|
||||
} catch (error) {
|
||||
console.error('❌ Session reset failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* User opt-out from tracking
|
||||
*/
|
||||
export const optOut = () => {
|
||||
try {
|
||||
posthog.opt_out_capturing();
|
||||
console.log('🚫 User opted out of tracking');
|
||||
} catch (error) {
|
||||
console.error('❌ Opt-out failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* User opt-in to tracking
|
||||
*/
|
||||
export const optIn = () => {
|
||||
try {
|
||||
posthog.opt_in_capturing();
|
||||
console.log('✅ User opted in to tracking');
|
||||
} catch (error) {
|
||||
console.error('❌ Opt-in failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if user has opted out
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const hasOptedOut = () => {
|
||||
try {
|
||||
return posthog.has_opted_out_capturing();
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to check opt-out status:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get feature flag value
|
||||
* @param {string} flagKey - Feature flag key
|
||||
* @param {any} defaultValue - Default value if flag not found
|
||||
* @returns {any} Feature flag value
|
||||
*/
|
||||
export const getFeatureFlag = (flagKey, defaultValue = false) => {
|
||||
try {
|
||||
return posthog.getFeatureFlag(flagKey) || defaultValue;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to get feature flag:', error);
|
||||
return defaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if feature flag is enabled
|
||||
* @param {string} flagKey - Feature flag key
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isFeatureEnabled = (flagKey) => {
|
||||
try {
|
||||
return posthog.isFeatureEnabled(flagKey);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to check feature flag:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default posthog;
|
||||
Reference in New Issue
Block a user