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.
- package/.claude/settings.local.json +12 -1
- package/.github/dependabot.yml +7 -0
- package/.github/workflows/node.js.yml +24 -1
- package/.vscode/settings.json +2 -1
- package/MixpanelReactNative.podspec +1 -1
- package/Samples/MixpanelExample/ios/MixpanelExample.xcworkspace/contents.xcworkspacedata +10 -0
- package/Samples/MixpanelExample/ios/Podfile.lock +1996 -0
- package/Samples/MixpanelStarter/.bundle/config +2 -0
- package/Samples/MixpanelStarter/.env.example +4 -0
- package/Samples/MixpanelStarter/.eslintrc.js +4 -0
- package/Samples/MixpanelStarter/.prettierrc.js +5 -0
- package/Samples/MixpanelStarter/.watchmanconfig +1 -0
- package/Samples/MixpanelStarter/App.tsx +10 -0
- package/Samples/MixpanelStarter/CLAUDE.md +538 -0
- package/Samples/MixpanelStarter/Gemfile +16 -0
- package/Samples/MixpanelStarter/INTEGRATION_GUIDE.md +606 -0
- package/Samples/MixpanelStarter/README.md +406 -0
- package/Samples/MixpanelStarter/__tests__/MixpanelContext.test.tsx +63 -0
- package/Samples/MixpanelStarter/android/app/build.gradle +119 -0
- package/Samples/MixpanelStarter/android/app/debug.keystore +0 -0
- package/Samples/MixpanelStarter/android/app/proguard-rules.pro +10 -0
- package/Samples/MixpanelStarter/android/app/src/main/AndroidManifest.xml +27 -0
- package/Samples/MixpanelStarter/android/app/src/main/java/com/mixpanelstarter/MainActivity.kt +22 -0
- package/Samples/MixpanelStarter/android/app/src/main/java/com/mixpanelstarter/MainApplication.kt +27 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/values/strings.xml +3 -0
- package/Samples/MixpanelStarter/android/app/src/main/res/values/styles.xml +9 -0
- package/Samples/MixpanelStarter/android/build.gradle +21 -0
- package/Samples/MixpanelStarter/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/Samples/MixpanelStarter/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/Samples/MixpanelStarter/android/gradle.properties +44 -0
- package/Samples/MixpanelStarter/android/gradlew +251 -0
- package/Samples/MixpanelStarter/android/gradlew.bat +99 -0
- package/Samples/MixpanelStarter/android/settings.gradle +6 -0
- package/Samples/MixpanelStarter/app.json +4 -0
- package/Samples/MixpanelStarter/babel.config.js +14 -0
- package/Samples/MixpanelStarter/index.js +9 -0
- package/Samples/MixpanelStarter/ios/.xcode.env +11 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/AppDelegate.swift +48 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/Images.xcassets/Contents.json +6 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/Info.plist +55 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/LaunchScreen.storyboard +47 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter/PrivacyInfo.xcprivacy +38 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter.xcodeproj/project.pbxproj +482 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter.xcodeproj/xcshareddata/xcschemes/MixpanelStarter.xcscheme +88 -0
- package/Samples/MixpanelStarter/ios/MixpanelStarter.xcworkspace/contents.xcworkspacedata +10 -0
- package/Samples/MixpanelStarter/ios/Podfile +34 -0
- package/Samples/MixpanelStarter/ios/Podfile.lock +2839 -0
- package/Samples/MixpanelStarter/jest.config.js +3 -0
- package/Samples/MixpanelStarter/metro.config.js +42 -0
- package/Samples/MixpanelStarter/package-lock.json +12141 -0
- package/Samples/MixpanelStarter/package.json +51 -0
- package/Samples/MixpanelStarter/src/@types/env.d.ts +3 -0
- package/Samples/MixpanelStarter/src/App.tsx +83 -0
- package/Samples/MixpanelStarter/src/components/ActionButton.tsx +92 -0
- package/Samples/MixpanelStarter/src/components/ErrorBoundary.tsx +81 -0
- package/Samples/MixpanelStarter/src/components/EventTrackingLog.tsx +163 -0
- package/Samples/MixpanelStarter/src/components/FlagCard.tsx +199 -0
- package/Samples/MixpanelStarter/src/components/InfoCard.tsx +77 -0
- package/Samples/MixpanelStarter/src/components/TestResultDisplay.tsx +181 -0
- package/Samples/MixpanelStarter/src/constants/tracking.ts +77 -0
- package/Samples/MixpanelStarter/src/contexts/MixpanelContext.tsx +159 -0
- package/Samples/MixpanelStarter/src/screens/FeatureFlagsScreen.tsx +1011 -0
- package/Samples/MixpanelStarter/src/screens/HomeScreen.tsx +307 -0
- package/Samples/MixpanelStarter/src/screens/OnboardingScreen.tsx +253 -0
- package/Samples/MixpanelStarter/src/screens/SettingsScreen.tsx +316 -0
- package/Samples/MixpanelStarter/src/types/flags.types.ts +42 -0
- package/Samples/MixpanelStarter/src/types/mixpanel.types.ts +26 -0
- package/Samples/MixpanelStarter/tsconfig.json +13 -0
- package/__tests__/flags.test.js +730 -0
- package/__tests__/index.test.js +7 -3
- package/__tests__/jest_setup.js +18 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/mixpanel/reactnative/MixpanelReactNativeModule.java +272 -2
- package/index.d.ts +64 -1
- package/index.js +42 -3
- package/ios/MixpanelReactNative.m +19 -1
- package/ios/MixpanelReactNative.swift +183 -5
- package/javascript/mixpanel-flags-js.js +463 -0
- package/javascript/mixpanel-flags.js +290 -0
- package/javascript/mixpanel-main.js +13 -1
- 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
|
+
};
|