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,307 @@
|
|
|
1
|
+
import React, {useState, useEffect} from 'react';
|
|
2
|
+
import {View, Text, StyleSheet, ScrollView, Switch, Alert} from 'react-native';
|
|
3
|
+
import {useMixpanel} from '../contexts/MixpanelContext';
|
|
4
|
+
import {ActionButton} from '../components/ActionButton';
|
|
5
|
+
import {InfoCard} from '../components/InfoCard';
|
|
6
|
+
import {Events, Properties} from '../constants/tracking';
|
|
7
|
+
|
|
8
|
+
export const HomeScreen: React.FC = () => {
|
|
9
|
+
const {mixpanel, isInitialized, track} = useMixpanel();
|
|
10
|
+
const [darkMode, setDarkMode] = useState(false);
|
|
11
|
+
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
|
|
12
|
+
const [videoPlaying, setVideoPlaying] = useState(false);
|
|
13
|
+
const [superProps, setSuperProps] = useState<Record<string, any>>({});
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
// Track screen view
|
|
17
|
+
if (isInitialized) {
|
|
18
|
+
track(Events.SCREEN_VIEWED, {
|
|
19
|
+
[Properties.SCREEN_NAME]: 'Home',
|
|
20
|
+
[Properties.TIMESTAMP]: new Date().toISOString(),
|
|
21
|
+
});
|
|
22
|
+
console.log('Tracked SCREEN_VIEWED for Home screen');
|
|
23
|
+
}
|
|
24
|
+
}, [isInitialized, track]);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
// Fetch and display current super properties
|
|
28
|
+
const fetchSuperProps = async () => {
|
|
29
|
+
if (mixpanel && isInitialized) {
|
|
30
|
+
try {
|
|
31
|
+
const props = await mixpanel.getSuperProperties();
|
|
32
|
+
setSuperProps(props);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Failed to get super properties:', error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
fetchSuperProps();
|
|
39
|
+
}, [mixpanel, isInitialized]);
|
|
40
|
+
|
|
41
|
+
const handleProductView = () => {
|
|
42
|
+
// Track event with rich properties
|
|
43
|
+
track(Events.PRODUCT_VIEWED, {
|
|
44
|
+
[Properties.PRODUCT_ID]: 'prod-123',
|
|
45
|
+
[Properties.PRODUCT_NAME]: 'Sample Product',
|
|
46
|
+
[Properties.PRODUCT_CATEGORY]: 'Electronics',
|
|
47
|
+
[Properties.TIMESTAMP]: new Date().toISOString(),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
Alert.alert('Product Viewed', 'Event tracked with product details!');
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleVideoStart = () => {
|
|
54
|
+
if (videoPlaying) {
|
|
55
|
+
Alert.alert('Already Playing', 'Video is already in progress');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Start timing the video event
|
|
60
|
+
mixpanel?.timeEvent(Events.VIDEO_COMPLETED);
|
|
61
|
+
setVideoPlaying(true);
|
|
62
|
+
|
|
63
|
+
track(Events.VIDEO_STARTED, {
|
|
64
|
+
[Properties.VIDEO_TITLE]: 'Introduction to Mixpanel',
|
|
65
|
+
[Properties.TIMESTAMP]: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
Alert.alert('Video Started', 'Timer started for video completion event');
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const handleVideoComplete = () => {
|
|
72
|
+
if (!videoPlaying) {
|
|
73
|
+
Alert.alert('Not Playing', 'Start the video first');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// This will automatically include the duration since timeEvent was called
|
|
78
|
+
track(Events.VIDEO_COMPLETED, {
|
|
79
|
+
[Properties.VIDEO_TITLE]: 'Introduction to Mixpanel',
|
|
80
|
+
[Properties.TIMESTAMP]: new Date().toISOString(),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
setVideoPlaying(false);
|
|
84
|
+
|
|
85
|
+
Alert.alert(
|
|
86
|
+
'Video Completed',
|
|
87
|
+
'Event tracked with automatic duration calculation!',
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleDarkModeToggle = async (value: boolean) => {
|
|
92
|
+
setDarkMode(value);
|
|
93
|
+
|
|
94
|
+
// Update super properties with the new preference
|
|
95
|
+
mixpanel?.registerSuperProperties({
|
|
96
|
+
[Properties.DARK_MODE_ENABLED]: value,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Track the toggle event
|
|
100
|
+
track(Events.DARK_MODE_TOGGLED, {
|
|
101
|
+
[Properties.DARK_MODE_ENABLED]: value,
|
|
102
|
+
[Properties.TIMESTAMP]: new Date().toISOString(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Refresh super properties display
|
|
106
|
+
const props = await mixpanel?.getSuperProperties();
|
|
107
|
+
if (props) {
|
|
108
|
+
setSuperProps(props);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Alert.alert(
|
|
112
|
+
'Dark Mode Updated',
|
|
113
|
+
`Dark mode ${value ? 'enabled' : 'disabled'}. This preference is now saved as a super property and will be included in all future events.`,
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleNotificationsToggle = async (value: boolean) => {
|
|
118
|
+
setNotificationsEnabled(value);
|
|
119
|
+
|
|
120
|
+
// Update super properties
|
|
121
|
+
mixpanel?.registerSuperProperties({
|
|
122
|
+
[Properties.NOTIFICATIONS_ENABLED]: value,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Track the toggle event
|
|
126
|
+
track(Events.NOTIFICATIONS_TOGGLED, {
|
|
127
|
+
[Properties.NOTIFICATIONS_ENABLED]: value,
|
|
128
|
+
[Properties.TIMESTAMP]: new Date().toISOString(),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Refresh super properties display
|
|
132
|
+
const props = await mixpanel?.getSuperProperties();
|
|
133
|
+
if (props) {
|
|
134
|
+
setSuperProps(props);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
|
140
|
+
<View style={styles.header}>
|
|
141
|
+
<Text style={styles.title}>Home</Text>
|
|
142
|
+
<Text style={styles.subtitle}>Event tracking and super properties</Text>
|
|
143
|
+
</View>
|
|
144
|
+
|
|
145
|
+
<View style={styles.section}>
|
|
146
|
+
<Text style={styles.sectionTitle}>Quick Actions</Text>
|
|
147
|
+
<Text style={styles.helper}>
|
|
148
|
+
Tap these buttons to track events with custom properties
|
|
149
|
+
</Text>
|
|
150
|
+
|
|
151
|
+
<ActionButton
|
|
152
|
+
title="View Product"
|
|
153
|
+
onPress={handleProductView}
|
|
154
|
+
disabled={!isInitialized}
|
|
155
|
+
style={styles.actionButton}
|
|
156
|
+
/>
|
|
157
|
+
|
|
158
|
+
<ActionButton
|
|
159
|
+
title={videoPlaying ? 'Video Playing...' : 'Start Video'}
|
|
160
|
+
onPress={handleVideoStart}
|
|
161
|
+
disabled={!isInitialized || videoPlaying}
|
|
162
|
+
variant={videoPlaying ? 'secondary' : 'primary'}
|
|
163
|
+
style={styles.actionButton}
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
<ActionButton
|
|
167
|
+
title="Complete Video"
|
|
168
|
+
onPress={handleVideoComplete}
|
|
169
|
+
disabled={!isInitialized || !videoPlaying}
|
|
170
|
+
style={styles.actionButton}
|
|
171
|
+
/>
|
|
172
|
+
</View>
|
|
173
|
+
|
|
174
|
+
<View style={styles.section}>
|
|
175
|
+
<Text style={styles.sectionTitle}>User Preferences</Text>
|
|
176
|
+
<Text style={styles.helper}>
|
|
177
|
+
These toggles save preferences as super properties that are included
|
|
178
|
+
in all events
|
|
179
|
+
</Text>
|
|
180
|
+
|
|
181
|
+
<View style={styles.preferenceRow}>
|
|
182
|
+
<View style={styles.preferenceLabel}>
|
|
183
|
+
<Text style={styles.preferenceTitle}>Dark Mode</Text>
|
|
184
|
+
<Text style={styles.preferenceSubtitle}>
|
|
185
|
+
Save as super property
|
|
186
|
+
</Text>
|
|
187
|
+
</View>
|
|
188
|
+
<Switch
|
|
189
|
+
value={darkMode}
|
|
190
|
+
onValueChange={handleDarkModeToggle}
|
|
191
|
+
disabled={!isInitialized}
|
|
192
|
+
/>
|
|
193
|
+
</View>
|
|
194
|
+
|
|
195
|
+
<View style={styles.preferenceRow}>
|
|
196
|
+
<View style={styles.preferenceLabel}>
|
|
197
|
+
<Text style={styles.preferenceTitle}>Notifications</Text>
|
|
198
|
+
<Text style={styles.preferenceSubtitle}>
|
|
199
|
+
Include in all events
|
|
200
|
+
</Text>
|
|
201
|
+
</View>
|
|
202
|
+
<Switch
|
|
203
|
+
value={notificationsEnabled}
|
|
204
|
+
onValueChange={handleNotificationsToggle}
|
|
205
|
+
disabled={!isInitialized}
|
|
206
|
+
/>
|
|
207
|
+
</View>
|
|
208
|
+
</View>
|
|
209
|
+
|
|
210
|
+
<InfoCard
|
|
211
|
+
title="Current Super Properties"
|
|
212
|
+
content={
|
|
213
|
+
Object.keys(superProps).length > 0
|
|
214
|
+
? superProps
|
|
215
|
+
: 'No super properties set'
|
|
216
|
+
}
|
|
217
|
+
/>
|
|
218
|
+
|
|
219
|
+
<InfoCard
|
|
220
|
+
title="What's Happening?"
|
|
221
|
+
content={`Events with Properties:
|
|
222
|
+
• track() sends events with custom data
|
|
223
|
+
• Properties provide context about user actions
|
|
224
|
+
• Use constants for event/property names
|
|
225
|
+
|
|
226
|
+
Timed Events:
|
|
227
|
+
• timeEvent() starts a timer for an event
|
|
228
|
+
• When you track that event, duration is auto-calculated
|
|
229
|
+
• Perfect for measuring video views, page reads, etc.
|
|
230
|
+
|
|
231
|
+
Super Properties:
|
|
232
|
+
• registerSuperProperties() sets global context
|
|
233
|
+
• Automatically included in every event
|
|
234
|
+
• Great for user preferences, app state, etc.
|
|
235
|
+
• getSuperProperties() retrieves current values`}
|
|
236
|
+
style={styles.infoCard}
|
|
237
|
+
/>
|
|
238
|
+
</ScrollView>
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const styles = StyleSheet.create({
|
|
243
|
+
container: {
|
|
244
|
+
flex: 1,
|
|
245
|
+
backgroundColor: '#fff',
|
|
246
|
+
},
|
|
247
|
+
content: {
|
|
248
|
+
padding: 20,
|
|
249
|
+
},
|
|
250
|
+
header: {
|
|
251
|
+
marginBottom: 20,
|
|
252
|
+
},
|
|
253
|
+
title: {
|
|
254
|
+
fontSize: 24,
|
|
255
|
+
fontWeight: 'bold',
|
|
256
|
+
color: '#333',
|
|
257
|
+
marginBottom: 8,
|
|
258
|
+
},
|
|
259
|
+
subtitle: {
|
|
260
|
+
fontSize: 16,
|
|
261
|
+
color: '#666',
|
|
262
|
+
},
|
|
263
|
+
section: {
|
|
264
|
+
marginBottom: 20,
|
|
265
|
+
},
|
|
266
|
+
sectionTitle: {
|
|
267
|
+
fontSize: 18,
|
|
268
|
+
fontWeight: '600',
|
|
269
|
+
color: '#333',
|
|
270
|
+
marginBottom: 8,
|
|
271
|
+
},
|
|
272
|
+
helper: {
|
|
273
|
+
fontSize: 14,
|
|
274
|
+
color: '#666',
|
|
275
|
+
marginBottom: 16,
|
|
276
|
+
lineHeight: 20,
|
|
277
|
+
},
|
|
278
|
+
actionButton: {
|
|
279
|
+
marginBottom: 12,
|
|
280
|
+
},
|
|
281
|
+
preferenceRow: {
|
|
282
|
+
flexDirection: 'row',
|
|
283
|
+
justifyContent: 'space-between',
|
|
284
|
+
alignItems: 'center',
|
|
285
|
+
paddingVertical: 12,
|
|
286
|
+
paddingHorizontal: 15,
|
|
287
|
+
backgroundColor: '#f9f9f9',
|
|
288
|
+
borderRadius: 8,
|
|
289
|
+
marginBottom: 10,
|
|
290
|
+
},
|
|
291
|
+
preferenceLabel: {
|
|
292
|
+
flex: 1,
|
|
293
|
+
},
|
|
294
|
+
preferenceTitle: {
|
|
295
|
+
fontSize: 16,
|
|
296
|
+
fontWeight: '500',
|
|
297
|
+
color: '#333',
|
|
298
|
+
marginBottom: 4,
|
|
299
|
+
},
|
|
300
|
+
preferenceSubtitle: {
|
|
301
|
+
fontSize: 13,
|
|
302
|
+
color: '#666',
|
|
303
|
+
},
|
|
304
|
+
infoCard: {
|
|
305
|
+
marginTop: 10,
|
|
306
|
+
},
|
|
307
|
+
});
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import React, {useState, useEffect} from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
TextInput,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
ScrollView,
|
|
8
|
+
Alert,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import {useMixpanel} from '../contexts/MixpanelContext';
|
|
11
|
+
import {ActionButton} from '../components/ActionButton';
|
|
12
|
+
import {InfoCard} from '../components/InfoCard';
|
|
13
|
+
import {Events, Properties} from '../constants/tracking';
|
|
14
|
+
|
|
15
|
+
export const OnboardingScreen: React.FC = () => {
|
|
16
|
+
const {mixpanel, isInitialized, track, identify, alias} = useMixpanel();
|
|
17
|
+
const [userId, setUserId] = useState('');
|
|
18
|
+
const [currentDistinctId, setCurrentDistinctId] = useState<string>('');
|
|
19
|
+
const [loading, setLoading] = useState(false);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
// Track screen view
|
|
23
|
+
if (isInitialized) {
|
|
24
|
+
track(Events.SCREEN_VIEWED, {
|
|
25
|
+
[Properties.SCREEN_NAME]: 'Onboarding',
|
|
26
|
+
[Properties.TIMESTAMP]: new Date().toISOString(),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}, [isInitialized, track]);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
// Get current distinct ID
|
|
33
|
+
const fetchDistinctId = async () => {
|
|
34
|
+
if (mixpanel && isInitialized) {
|
|
35
|
+
try {
|
|
36
|
+
const id = await mixpanel.getDistinctId();
|
|
37
|
+
setCurrentDistinctId(id);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Failed to get distinct ID:', error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
fetchDistinctId();
|
|
44
|
+
}, [mixpanel, isInitialized]);
|
|
45
|
+
|
|
46
|
+
const handleSignUp = async () => {
|
|
47
|
+
if (!userId.trim()) {
|
|
48
|
+
Alert.alert('Error', 'Please enter a user ID or email');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setLoading(true);
|
|
53
|
+
try {
|
|
54
|
+
const previousId = currentDistinctId;
|
|
55
|
+
|
|
56
|
+
// 1. Identify the user with the new ID
|
|
57
|
+
identify(userId);
|
|
58
|
+
|
|
59
|
+
// 2. Link the previous anonymous ID to the new identified ID
|
|
60
|
+
await alias(userId, previousId);
|
|
61
|
+
|
|
62
|
+
// 3. Set user profile properties
|
|
63
|
+
mixpanel?.getPeople().set({
|
|
64
|
+
$email: userId,
|
|
65
|
+
$name: userId.split('@')[0], // Extract name from email
|
|
66
|
+
signup_date: new Date().toISOString(),
|
|
67
|
+
signup_method: 'app',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 4. Set properties that should only be set once
|
|
71
|
+
mixpanel?.getPeople().setOnce({
|
|
72
|
+
first_app_open: new Date().toISOString(),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// 5. Track the signup event
|
|
76
|
+
track(Events.USER_SIGNED_UP, {
|
|
77
|
+
[Properties.USER_ID]: userId,
|
|
78
|
+
[Properties.USER_EMAIL]: userId,
|
|
79
|
+
[Properties.TIMESTAMP]: new Date().toISOString(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
Alert.alert(
|
|
83
|
+
'Success!',
|
|
84
|
+
`Welcome ${userId}! You've been identified and your profile has been set up.`,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Update the displayed distinct ID
|
|
88
|
+
const newId = await mixpanel?.getDistinctId();
|
|
89
|
+
if (newId) {
|
|
90
|
+
setCurrentDistinctId(newId);
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Signup failed:', error);
|
|
94
|
+
Alert.alert('Error', 'Failed to sign up. Please try again.');
|
|
95
|
+
} finally {
|
|
96
|
+
setLoading(false);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleGuestContinue = () => {
|
|
101
|
+
// Track event for anonymous user
|
|
102
|
+
track(Events.GUEST_CONTINUED, {
|
|
103
|
+
[Properties.TIMESTAMP]: new Date().toISOString(),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
Alert.alert(
|
|
107
|
+
'Guest Mode',
|
|
108
|
+
'You can continue using the app. Events will be tracked with your anonymous ID.',
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
|
114
|
+
<View style={styles.header}>
|
|
115
|
+
<Text style={styles.title}>Welcome to MixpanelStarter</Text>
|
|
116
|
+
<Text style={styles.subtitle}>
|
|
117
|
+
This screen demonstrates user identification
|
|
118
|
+
</Text>
|
|
119
|
+
</View>
|
|
120
|
+
|
|
121
|
+
<InfoCard
|
|
122
|
+
title="Your Current Distinct ID"
|
|
123
|
+
content={currentDistinctId || 'Loading...'}
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
<View style={styles.section}>
|
|
127
|
+
<Text style={styles.sectionTitle}>Sign Up or Log In</Text>
|
|
128
|
+
<Text style={styles.helper}>
|
|
129
|
+
Enter your email to identify yourself and set up your user profile.
|
|
130
|
+
</Text>
|
|
131
|
+
|
|
132
|
+
<TextInput
|
|
133
|
+
style={styles.input}
|
|
134
|
+
placeholder="Enter your email"
|
|
135
|
+
value={userId}
|
|
136
|
+
onChangeText={setUserId}
|
|
137
|
+
autoCapitalize="none"
|
|
138
|
+
keyboardType="email-address"
|
|
139
|
+
editable={isInitialized}
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
<ActionButton
|
|
143
|
+
title="Sign Up"
|
|
144
|
+
onPress={handleSignUp}
|
|
145
|
+
disabled={!isInitialized || !userId.trim()}
|
|
146
|
+
loading={loading}
|
|
147
|
+
/>
|
|
148
|
+
</View>
|
|
149
|
+
|
|
150
|
+
<View style={styles.divider}>
|
|
151
|
+
<View style={styles.dividerLine} />
|
|
152
|
+
<Text style={styles.dividerText}>OR</Text>
|
|
153
|
+
<View style={styles.dividerLine} />
|
|
154
|
+
</View>
|
|
155
|
+
|
|
156
|
+
<View style={styles.section}>
|
|
157
|
+
<Text style={styles.sectionTitle}>Continue as Guest</Text>
|
|
158
|
+
<Text style={styles.helper}>
|
|
159
|
+
Use the app anonymously. Events will be tracked with your current ID.
|
|
160
|
+
</Text>
|
|
161
|
+
|
|
162
|
+
<ActionButton
|
|
163
|
+
title="Continue as Guest"
|
|
164
|
+
onPress={handleGuestContinue}
|
|
165
|
+
variant="secondary"
|
|
166
|
+
disabled={!isInitialized}
|
|
167
|
+
/>
|
|
168
|
+
</View>
|
|
169
|
+
|
|
170
|
+
<InfoCard
|
|
171
|
+
title="What's Happening?"
|
|
172
|
+
content={`When you sign up:
|
|
173
|
+
• identify() sets your user ID
|
|
174
|
+
• alias() links your previous anonymous events
|
|
175
|
+
• getPeople().set() creates your user profile
|
|
176
|
+
• getPeople().setOnce() sets properties only if they don't exist
|
|
177
|
+
• track() logs the signup event
|
|
178
|
+
|
|
179
|
+
When you continue as guest:
|
|
180
|
+
• Events are tracked with your anonymous ID
|
|
181
|
+
• You can identify later to claim these events`}
|
|
182
|
+
style={styles.infoCard}
|
|
183
|
+
/>
|
|
184
|
+
</ScrollView>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const styles = StyleSheet.create({
|
|
189
|
+
container: {
|
|
190
|
+
flex: 1,
|
|
191
|
+
backgroundColor: '#fff',
|
|
192
|
+
},
|
|
193
|
+
content: {
|
|
194
|
+
padding: 20,
|
|
195
|
+
},
|
|
196
|
+
header: {
|
|
197
|
+
marginBottom: 20,
|
|
198
|
+
},
|
|
199
|
+
title: {
|
|
200
|
+
fontSize: 24,
|
|
201
|
+
fontWeight: 'bold',
|
|
202
|
+
color: '#333',
|
|
203
|
+
marginBottom: 8,
|
|
204
|
+
},
|
|
205
|
+
subtitle: {
|
|
206
|
+
fontSize: 16,
|
|
207
|
+
color: '#666',
|
|
208
|
+
},
|
|
209
|
+
section: {
|
|
210
|
+
marginBottom: 20,
|
|
211
|
+
},
|
|
212
|
+
sectionTitle: {
|
|
213
|
+
fontSize: 18,
|
|
214
|
+
fontWeight: '600',
|
|
215
|
+
color: '#333',
|
|
216
|
+
marginBottom: 8,
|
|
217
|
+
},
|
|
218
|
+
helper: {
|
|
219
|
+
fontSize: 14,
|
|
220
|
+
color: '#666',
|
|
221
|
+
marginBottom: 16,
|
|
222
|
+
lineHeight: 20,
|
|
223
|
+
},
|
|
224
|
+
input: {
|
|
225
|
+
borderWidth: 1,
|
|
226
|
+
borderColor: '#ddd',
|
|
227
|
+
borderRadius: 8,
|
|
228
|
+
paddingHorizontal: 15,
|
|
229
|
+
paddingVertical: 12,
|
|
230
|
+
fontSize: 16,
|
|
231
|
+
marginBottom: 16,
|
|
232
|
+
backgroundColor: '#fff',
|
|
233
|
+
},
|
|
234
|
+
divider: {
|
|
235
|
+
flexDirection: 'row',
|
|
236
|
+
alignItems: 'center',
|
|
237
|
+
marginVertical: 20,
|
|
238
|
+
},
|
|
239
|
+
dividerLine: {
|
|
240
|
+
flex: 1,
|
|
241
|
+
height: 1,
|
|
242
|
+
backgroundColor: '#ddd',
|
|
243
|
+
},
|
|
244
|
+
dividerText: {
|
|
245
|
+
marginHorizontal: 10,
|
|
246
|
+
color: '#999',
|
|
247
|
+
fontSize: 14,
|
|
248
|
+
fontWeight: '500',
|
|
249
|
+
},
|
|
250
|
+
infoCard: {
|
|
251
|
+
marginTop: 10,
|
|
252
|
+
},
|
|
253
|
+
});
|