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,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
+ });