mixpanel-react-native 3.1.2 → 3.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/.claude/settings.local.json +12 -1
  2. package/.github/dependabot.yml +7 -0
  3. package/.github/workflows/node.js.yml +24 -1
  4. package/.vscode/settings.json +2 -1
  5. package/MixpanelReactNative.podspec +1 -1
  6. package/Samples/MixpanelExample/ios/MixpanelExample.xcworkspace/contents.xcworkspacedata +10 -0
  7. package/Samples/MixpanelExample/ios/Podfile.lock +1996 -0
  8. package/Samples/MixpanelStarter/.bundle/config +2 -0
  9. package/Samples/MixpanelStarter/.env.example +4 -0
  10. package/Samples/MixpanelStarter/.eslintrc.js +4 -0
  11. package/Samples/MixpanelStarter/.prettierrc.js +5 -0
  12. package/Samples/MixpanelStarter/.watchmanconfig +1 -0
  13. package/Samples/MixpanelStarter/App.tsx +10 -0
  14. package/Samples/MixpanelStarter/CLAUDE.md +538 -0
  15. package/Samples/MixpanelStarter/Gemfile +16 -0
  16. package/Samples/MixpanelStarter/INTEGRATION_GUIDE.md +606 -0
  17. package/Samples/MixpanelStarter/README.md +406 -0
  18. package/Samples/MixpanelStarter/__tests__/MixpanelContext.test.tsx +63 -0
  19. package/Samples/MixpanelStarter/android/app/build.gradle +119 -0
  20. package/Samples/MixpanelStarter/android/app/debug.keystore +0 -0
  21. package/Samples/MixpanelStarter/android/app/proguard-rules.pro +10 -0
  22. package/Samples/MixpanelStarter/android/app/src/main/AndroidManifest.xml +27 -0
  23. package/Samples/MixpanelStarter/android/app/src/main/java/com/mixpanelstarter/MainActivity.kt +22 -0
  24. package/Samples/MixpanelStarter/android/app/src/main/java/com/mixpanelstarter/MainApplication.kt +27 -0
  25. package/Samples/MixpanelStarter/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  26. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  27. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  28. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  29. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  30. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  31. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  32. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  33. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  34. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  35. package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  36. package/Samples/MixpanelStarter/android/app/src/main/res/values/strings.xml +3 -0
  37. package/Samples/MixpanelStarter/android/app/src/main/res/values/styles.xml +9 -0
  38. package/Samples/MixpanelStarter/android/build.gradle +21 -0
  39. package/Samples/MixpanelStarter/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  40. package/Samples/MixpanelStarter/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  41. package/Samples/MixpanelStarter/android/gradle.properties +44 -0
  42. package/Samples/MixpanelStarter/android/gradlew +251 -0
  43. package/Samples/MixpanelStarter/android/gradlew.bat +99 -0
  44. package/Samples/MixpanelStarter/android/settings.gradle +6 -0
  45. package/Samples/MixpanelStarter/app.json +4 -0
  46. package/Samples/MixpanelStarter/babel.config.js +14 -0
  47. package/Samples/MixpanelStarter/index.js +9 -0
  48. package/Samples/MixpanelStarter/ios/.xcode.env +11 -0
  49. package/Samples/MixpanelStarter/ios/MixpanelStarter/AppDelegate.swift +48 -0
  50. package/Samples/MixpanelStarter/ios/MixpanelStarter/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  51. package/Samples/MixpanelStarter/ios/MixpanelStarter/Images.xcassets/Contents.json +6 -0
  52. package/Samples/MixpanelStarter/ios/MixpanelStarter/Info.plist +55 -0
  53. package/Samples/MixpanelStarter/ios/MixpanelStarter/LaunchScreen.storyboard +47 -0
  54. package/Samples/MixpanelStarter/ios/MixpanelStarter/PrivacyInfo.xcprivacy +38 -0
  55. package/Samples/MixpanelStarter/ios/MixpanelStarter.xcodeproj/project.pbxproj +482 -0
  56. package/Samples/MixpanelStarter/ios/MixpanelStarter.xcodeproj/xcshareddata/xcschemes/MixpanelStarter.xcscheme +88 -0
  57. package/Samples/MixpanelStarter/ios/MixpanelStarter.xcworkspace/contents.xcworkspacedata +10 -0
  58. package/Samples/MixpanelStarter/ios/Podfile +34 -0
  59. package/Samples/MixpanelStarter/ios/Podfile.lock +2839 -0
  60. package/Samples/MixpanelStarter/jest.config.js +3 -0
  61. package/Samples/MixpanelStarter/metro.config.js +42 -0
  62. package/Samples/MixpanelStarter/package-lock.json +12141 -0
  63. package/Samples/MixpanelStarter/package.json +51 -0
  64. package/Samples/MixpanelStarter/src/@types/env.d.ts +3 -0
  65. package/Samples/MixpanelStarter/src/App.tsx +83 -0
  66. package/Samples/MixpanelStarter/src/components/ActionButton.tsx +92 -0
  67. package/Samples/MixpanelStarter/src/components/ErrorBoundary.tsx +81 -0
  68. package/Samples/MixpanelStarter/src/components/EventTrackingLog.tsx +163 -0
  69. package/Samples/MixpanelStarter/src/components/FlagCard.tsx +199 -0
  70. package/Samples/MixpanelStarter/src/components/InfoCard.tsx +77 -0
  71. package/Samples/MixpanelStarter/src/components/TestResultDisplay.tsx +181 -0
  72. package/Samples/MixpanelStarter/src/constants/tracking.ts +77 -0
  73. package/Samples/MixpanelStarter/src/contexts/MixpanelContext.tsx +159 -0
  74. package/Samples/MixpanelStarter/src/screens/FeatureFlagsScreen.tsx +1011 -0
  75. package/Samples/MixpanelStarter/src/screens/HomeScreen.tsx +307 -0
  76. package/Samples/MixpanelStarter/src/screens/OnboardingScreen.tsx +253 -0
  77. package/Samples/MixpanelStarter/src/screens/SettingsScreen.tsx +316 -0
  78. package/Samples/MixpanelStarter/src/types/flags.types.ts +42 -0
  79. package/Samples/MixpanelStarter/src/types/mixpanel.types.ts +26 -0
  80. package/Samples/MixpanelStarter/tsconfig.json +13 -0
  81. package/__tests__/flags.test.js +730 -0
  82. package/__tests__/index.test.js +7 -3
  83. package/__tests__/jest_setup.js +18 -0
  84. package/android/build.gradle +1 -1
  85. package/android/src/main/java/com/mixpanel/reactnative/MixpanelReactNativeModule.java +272 -2
  86. package/index.d.ts +64 -1
  87. package/index.js +42 -3
  88. package/ios/MixpanelReactNative.m +19 -1
  89. package/ios/MixpanelReactNative.swift +183 -5
  90. package/javascript/mixpanel-flags-js.js +463 -0
  91. package/javascript/mixpanel-flags.js +290 -0
  92. package/javascript/mixpanel-main.js +13 -1
  93. package/package.json +2 -2
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import {View, Text, StyleSheet, ViewStyle} from 'react-native';
3
+
4
+ interface InfoCardProps {
5
+ title: string;
6
+ content: string | Record<string, any>;
7
+ style?: ViewStyle;
8
+ }
9
+
10
+ export const InfoCard: React.FC<InfoCardProps> = ({title, content, style}) => {
11
+ const renderContent = () => {
12
+ if (typeof content === 'string') {
13
+ return <Text style={styles.content}>{content}</Text>;
14
+ }
15
+
16
+ // Render object as key-value pairs
17
+ return (
18
+ <View style={styles.kvContainer}>
19
+ {Object.entries(content).map(([key, value]) => (
20
+ <View key={key} style={styles.kvRow}>
21
+ <Text style={styles.key}>{key}:</Text>
22
+ <Text style={styles.value}>{JSON.stringify(value)}</Text>
23
+ </View>
24
+ ))}
25
+ </View>
26
+ );
27
+ };
28
+
29
+ return (
30
+ <View style={[styles.card, style]}>
31
+ <Text style={styles.title}>{title}</Text>
32
+ {renderContent()}
33
+ </View>
34
+ );
35
+ };
36
+
37
+ const styles = StyleSheet.create({
38
+ card: {
39
+ backgroundColor: '#f9f9f9',
40
+ borderRadius: 8,
41
+ padding: 15,
42
+ marginVertical: 8,
43
+ borderWidth: 1,
44
+ borderColor: '#e0e0e0',
45
+ },
46
+ title: {
47
+ fontSize: 14,
48
+ fontWeight: '600',
49
+ color: '#666',
50
+ marginBottom: 8,
51
+ textTransform: 'uppercase',
52
+ letterSpacing: 0.5,
53
+ },
54
+ content: {
55
+ fontSize: 14,
56
+ color: '#333',
57
+ lineHeight: 20,
58
+ },
59
+ kvContainer: {
60
+ gap: 6,
61
+ },
62
+ kvRow: {
63
+ flexDirection: 'row',
64
+ gap: 8,
65
+ },
66
+ key: {
67
+ fontSize: 14,
68
+ fontWeight: '500',
69
+ color: '#666',
70
+ minWidth: 120,
71
+ },
72
+ value: {
73
+ fontSize: 14,
74
+ color: '#333',
75
+ flex: 1,
76
+ },
77
+ });
@@ -0,0 +1,181 @@
1
+ import React from 'react';
2
+ import {View, Text, StyleSheet, ScrollView} from 'react-native';
3
+ import {TestResult} from '../types/flags.types';
4
+
5
+ interface TestResultDisplayProps {
6
+ result: TestResult | null;
7
+ }
8
+
9
+ const formatResult = (value: any): string => {
10
+ if (value === null) return 'null';
11
+ if (value === undefined) return 'undefined';
12
+ if (typeof value === 'object') {
13
+ return JSON.stringify(value, null, 2);
14
+ }
15
+ if (typeof value === 'string') {
16
+ return `"${value}"`;
17
+ }
18
+ return String(value);
19
+ };
20
+
21
+ export const TestResultDisplay: React.FC<TestResultDisplayProps> = ({result}) => {
22
+ if (!result) {
23
+ return (
24
+ <View style={styles.container}>
25
+ <Text style={styles.placeholder}>
26
+ No test results yet. Run a test to see results here.
27
+ </Text>
28
+ </View>
29
+ );
30
+ }
31
+
32
+ return (
33
+ <ScrollView style={styles.container}>
34
+ <View style={styles.header}>
35
+ <Text style={styles.title}>Test Result</Text>
36
+ <Text style={styles.timestamp}>
37
+ {result.timestamp.toLocaleTimeString()}
38
+ </Text>
39
+ </View>
40
+
41
+ <View style={styles.section}>
42
+ <Text style={styles.sectionTitle}>Method Called</Text>
43
+ <Text style={styles.codeText}>{result.method}</Text>
44
+ </View>
45
+
46
+ <View style={styles.row}>
47
+ <View style={styles.col}>
48
+ <Text style={styles.label}>Flag:</Text>
49
+ <Text style={styles.value}>{result.flagName}</Text>
50
+ </View>
51
+ <View style={styles.col}>
52
+ <Text style={styles.label}>Type:</Text>
53
+ <Text style={styles.value}>{result.resultType}</Text>
54
+ </View>
55
+ </View>
56
+
57
+ <View style={styles.row}>
58
+ <View style={styles.col}>
59
+ <Text style={styles.label}>Used Fallback:</Text>
60
+ <Text style={[styles.value, result.usedFallback && styles.warning]}>
61
+ {result.usedFallback ? '⚠️ Yes' : '✅ No'}
62
+ </Text>
63
+ </View>
64
+ <View style={styles.col}>
65
+ <Text style={styles.label}>Time:</Text>
66
+ <Text style={styles.value}>{result.executionTime}ms</Text>
67
+ </View>
68
+ </View>
69
+
70
+ {result.usedFallback && (
71
+ <View style={styles.section}>
72
+ <Text style={styles.sectionTitle}>Fallback Value</Text>
73
+ <Text style={styles.codeText}>{formatResult(result.fallback)}</Text>
74
+ </View>
75
+ )}
76
+
77
+ <View style={styles.section}>
78
+ <Text style={styles.sectionTitle}>Returned Value</Text>
79
+ <Text style={styles.codeText}>{formatResult(result.result)}</Text>
80
+ </View>
81
+
82
+ {result.error && (
83
+ <View style={[styles.section, styles.errorSection]}>
84
+ <Text style={styles.errorTitle}>❌ Error</Text>
85
+ <Text style={styles.errorText}>{result.error}</Text>
86
+ </View>
87
+ )}
88
+ </ScrollView>
89
+ );
90
+ };
91
+
92
+ const styles = StyleSheet.create({
93
+ container: {
94
+ backgroundColor: '#fff',
95
+ borderRadius: 8,
96
+ borderWidth: 1,
97
+ borderColor: '#e0e0e0',
98
+ padding: 16,
99
+ maxHeight: 400,
100
+ },
101
+ placeholder: {
102
+ textAlign: 'center',
103
+ color: '#999',
104
+ fontSize: 14,
105
+ padding: 20,
106
+ },
107
+ header: {
108
+ flexDirection: 'row',
109
+ justifyContent: 'space-between',
110
+ alignItems: 'center',
111
+ marginBottom: 16,
112
+ paddingBottom: 12,
113
+ borderBottomWidth: 1,
114
+ borderBottomColor: '#e0e0e0',
115
+ },
116
+ title: {
117
+ fontSize: 18,
118
+ fontWeight: '600',
119
+ color: '#333',
120
+ },
121
+ timestamp: {
122
+ fontSize: 12,
123
+ color: '#666',
124
+ },
125
+ section: {
126
+ marginBottom: 16,
127
+ },
128
+ sectionTitle: {
129
+ fontSize: 14,
130
+ fontWeight: '600',
131
+ color: '#666',
132
+ marginBottom: 8,
133
+ },
134
+ codeText: {
135
+ fontFamily: 'Courier',
136
+ fontSize: 13,
137
+ color: '#333',
138
+ backgroundColor: '#f5f5f5',
139
+ padding: 12,
140
+ borderRadius: 4,
141
+ },
142
+ row: {
143
+ flexDirection: 'row',
144
+ marginBottom: 12,
145
+ },
146
+ col: {
147
+ flex: 1,
148
+ },
149
+ label: {
150
+ fontSize: 13,
151
+ fontWeight: '500',
152
+ color: '#666',
153
+ marginBottom: 4,
154
+ },
155
+ value: {
156
+ fontSize: 14,
157
+ color: '#333',
158
+ fontFamily: 'Courier',
159
+ },
160
+ warning: {
161
+ color: '#ff9800',
162
+ },
163
+ errorSection: {
164
+ backgroundColor: '#fff3f3',
165
+ padding: 12,
166
+ borderRadius: 4,
167
+ borderWidth: 1,
168
+ borderColor: '#ffcdd2',
169
+ },
170
+ errorTitle: {
171
+ fontSize: 14,
172
+ fontWeight: '600',
173
+ color: '#d32f2f',
174
+ marginBottom: 8,
175
+ },
176
+ errorText: {
177
+ fontSize: 13,
178
+ color: '#d32f2f',
179
+ fontFamily: 'Courier',
180
+ },
181
+ });
@@ -0,0 +1,77 @@
1
+ // Event Names
2
+ export const Events = {
3
+ // Screen Views
4
+ SCREEN_VIEWED: 'Screen Viewed',
5
+
6
+ // User Actions
7
+ USER_SIGNED_UP: 'User Signed Up',
8
+ USER_LOGGED_IN: 'User Logged In',
9
+ USER_LOGGED_OUT: 'User Logged Out',
10
+ GUEST_CONTINUED: 'Guest Continued',
11
+
12
+ // Content Interactions
13
+ PRODUCT_VIEWED: 'Product Viewed',
14
+ VIDEO_STARTED: 'Video Started',
15
+ VIDEO_COMPLETED: 'Video Completed',
16
+
17
+ // Settings
18
+ DARK_MODE_TOGGLED: 'Dark Mode Toggled',
19
+ NOTIFICATIONS_TOGGLED: 'Notifications Toggled',
20
+ TRACKING_OPTED_IN: 'Tracking Opted In',
21
+ TRACKING_OPTED_OUT: 'Tracking Opted Out',
22
+ DATA_RESET: 'Data Reset',
23
+ EVENTS_FLUSHED: 'Events Flushed',
24
+
25
+ // Feature Flags
26
+ FLAGS_LOADED: 'Feature Flags Loaded',
27
+ FLAG_CHECKED: 'Feature Flag Checked',
28
+ FLAG_CONTEXT_UPDATED: 'Feature Flag Context Updated',
29
+ FLAG_TEST_SYNC: 'Flag Test Sync',
30
+ FLAG_TEST_ASYNC: 'Flag Test Async',
31
+ FLAG_TEST_CALLBACK: 'Flag Test Callback',
32
+ FLAG_TEST_EDGE_CASE: 'Flag Test Edge Case',
33
+ FLAG_TEST_COERCION: 'Flag Test Type Coercion',
34
+ } as const;
35
+
36
+ // Property Names
37
+ export const Properties = {
38
+ // Screen properties
39
+ SCREEN_NAME: 'screen_name',
40
+
41
+ // User properties
42
+ USER_ID: 'user_id',
43
+ USER_EMAIL: 'user_email',
44
+ SIGNUP_DATE: 'signup_date',
45
+
46
+ // Content properties
47
+ PRODUCT_ID: 'product_id',
48
+ PRODUCT_NAME: 'product_name',
49
+ PRODUCT_CATEGORY: 'product_category',
50
+ VIDEO_TITLE: 'video_title',
51
+ VIDEO_DURATION: 'video_duration',
52
+
53
+ // Settings properties
54
+ DARK_MODE_ENABLED: 'dark_mode_enabled',
55
+ NOTIFICATIONS_ENABLED: 'notifications_enabled',
56
+
57
+ // Feature Flag properties
58
+ FLAG_KEY: 'flag_key',
59
+ FLAG_ENABLED: 'flag_enabled',
60
+ FLAG_VALUE: 'flag_value',
61
+ FLAG_METHOD: 'flag_method',
62
+ FLAG_EXECUTION_TIME: 'flag_execution_time',
63
+ FLAG_USED_FALLBACK: 'flag_used_fallback',
64
+ FLAG_RESULT_TYPE: 'flag_result_type',
65
+
66
+ // Metadata
67
+ TIMESTAMP: 'timestamp',
68
+ APP_VERSION: 'app_version',
69
+ PLATFORM: 'platform',
70
+ } as const;
71
+
72
+ // Super Property Keys
73
+ export const SuperProperties = {
74
+ APP_VERSION: 'App Version',
75
+ PLATFORM: 'Platform',
76
+ ENVIRONMENT: 'Environment',
77
+ } as const;
@@ -0,0 +1,159 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useState,
6
+ useCallback,
7
+ ReactNode,
8
+ } from 'react';
9
+ import {Mixpanel} from 'mixpanel-react-native';
10
+ import {Platform} from 'react-native';
11
+ import AsyncStorage from '@react-native-async-storage/async-storage';
12
+ import {MixpanelContextValue} from '../types/mixpanel.types';
13
+ import {SuperProperties} from '../constants/tracking';
14
+
15
+ const MixpanelContext = createContext<MixpanelContextValue | undefined>(
16
+ undefined,
17
+ );
18
+
19
+ interface MixpanelProviderProps {
20
+ children: ReactNode;
21
+ token: string;
22
+ trackAutomaticEvents?: boolean;
23
+ useNative?: boolean;
24
+ }
25
+
26
+ export const MixpanelProvider: React.FC<MixpanelProviderProps> = ({
27
+ children,
28
+ token,
29
+ trackAutomaticEvents = true,
30
+ useNative = true,
31
+ }) => {
32
+ const [mixpanel, setMixpanel] = useState<Mixpanel | null>(null);
33
+ const [isInitialized, setIsInitialized] = useState(false);
34
+ const [isLoading, setIsLoading] = useState(true);
35
+ const [error, setError] = useState<Error | null>(null);
36
+
37
+ useEffect(() => {
38
+ const initMixpanel = async () => {
39
+ try {
40
+ setIsLoading(true);
41
+ setError(null);
42
+
43
+ // Create Mixpanel instance
44
+ const instance = useNative
45
+ ? new Mixpanel(token, trackAutomaticEvents, true)
46
+ : new Mixpanel(token, trackAutomaticEvents, false, AsyncStorage);
47
+
48
+ // Initialize with feature flags enabled
49
+ await instance.init(false, {}, undefined, false, {
50
+ enabled: true,
51
+ });
52
+
53
+ // Set up default super properties
54
+ instance.registerSuperProperties({
55
+ [SuperProperties.APP_VERSION]: '1.0.0',
56
+ [SuperProperties.PLATFORM]: Platform.OS,
57
+ [SuperProperties.ENVIRONMENT]: __DEV__ ? 'development' : 'production',
58
+ });
59
+ // Enable logging for debugging
60
+ instance.setLoggingEnabled(__DEV__);
61
+
62
+ setMixpanel(instance);
63
+ setIsInitialized(true);
64
+ } catch (err) {
65
+ const error = err instanceof Error ? err : new Error(String(err));
66
+ setError(error);
67
+ console.error('Failed to initialize Mixpanel:', error);
68
+ } finally {
69
+ setIsLoading(false);
70
+ }
71
+ };
72
+
73
+ initMixpanel();
74
+ }, [token, trackAutomaticEvents, useNative]);
75
+
76
+ // Convenience wrapper methods
77
+ const track = useCallback(
78
+ (eventName: string, properties?: Record<string, any>) => {
79
+ if (!mixpanel || !isInitialized) {
80
+ console.warn(
81
+ 'Mixpanel not initialized yet. Event not tracked:',
82
+ eventName,
83
+ );
84
+ return;
85
+ }
86
+ mixpanel.track(eventName, properties);
87
+ },
88
+ [mixpanel, isInitialized],
89
+ );
90
+
91
+ const identify = useCallback(
92
+ (distinctId: string) => {
93
+ if (!mixpanel || !isInitialized) {
94
+ console.warn('Mixpanel not initialized yet. Identify skipped.');
95
+ return;
96
+ }
97
+ mixpanel.identify(distinctId);
98
+ },
99
+ [mixpanel, isInitialized],
100
+ );
101
+
102
+ const alias = useCallback(
103
+ async (aliasValue: string, distinctId?: string) => {
104
+ if (!mixpanel || !isInitialized) {
105
+ console.warn('Mixpanel not initialized yet. Alias skipped.');
106
+ return;
107
+ }
108
+ const currentId = distinctId || (await mixpanel.getDistinctId());
109
+ mixpanel.alias(aliasValue, currentId);
110
+ },
111
+ [mixpanel, isInitialized],
112
+ );
113
+
114
+ const reset = useCallback(() => {
115
+ if (!mixpanel || !isInitialized) {
116
+ console.warn('Mixpanel not initialized yet. Reset skipped.');
117
+ return;
118
+ }
119
+ mixpanel.reset();
120
+ }, [mixpanel, isInitialized]);
121
+
122
+ const flush = useCallback(async () => {
123
+ if (!mixpanel || !isInitialized) {
124
+ console.warn('Mixpanel not initialized yet. Flush skipped.');
125
+ return;
126
+ }
127
+ await mixpanel.flush();
128
+ }, [mixpanel, isInitialized]);
129
+
130
+ const value: MixpanelContextValue = {
131
+ mixpanel,
132
+ isInitialized,
133
+ isLoading,
134
+ error,
135
+ track,
136
+ identify,
137
+ alias,
138
+ reset,
139
+ flush,
140
+ };
141
+
142
+ return (
143
+ <MixpanelContext.Provider value={value}>
144
+ {children}
145
+ </MixpanelContext.Provider>
146
+ );
147
+ };
148
+
149
+ /**
150
+ * Hook to access Mixpanel instance and convenience methods
151
+ * @throws Error if used outside of MixpanelProvider
152
+ */
153
+ export const useMixpanel = (): MixpanelContextValue => {
154
+ const context = useContext(MixpanelContext);
155
+ if (context === undefined) {
156
+ throw new Error('useMixpanel must be used within a MixpanelProvider');
157
+ }
158
+ return context;
159
+ };