@umituz/react-native-onboarding 2.2.0 → 2.3.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/README.md +65 -0
- package/package.json +6 -1
- package/src/domain/entities/OnboardingOptions.ts +12 -0
- package/src/domain/entities/OnboardingUserData.ts +12 -0
- package/src/index.ts +1 -0
- package/src/infrastructure/services/OnboardingDeviceTrackingService.ts +60 -0
- package/src/infrastructure/storage/OnboardingStore.ts +17 -2
- package/src/presentation/screens/OnboardingScreen.tsx +27 -11
package/README.md
CHANGED
|
@@ -185,6 +185,8 @@ const slides: OnboardingSlide[] = [
|
|
|
185
185
|
| `showProgressText` | `boolean` | `true` | Show progress text (1 of 5) |
|
|
186
186
|
| `storageKey` | `string` | - | Custom storage key for completion state |
|
|
187
187
|
| `autoComplete` | `boolean` | `false` | Auto-complete on last slide |
|
|
188
|
+
| `enableDeviceTracking` | `boolean` | `false` | Collect device info during onboarding |
|
|
189
|
+
| `userId` | `string` | - | User ID for device tracking (optional) |
|
|
188
190
|
|
|
189
191
|
### OnboardingSlide Interface
|
|
190
192
|
|
|
@@ -328,6 +330,69 @@ const isComplete = userData.completedAt !== undefined;
|
|
|
328
330
|
|
|
329
331
|
// Check if onboarding was skipped
|
|
330
332
|
const wasSkipped = userData.skipped === true;
|
|
333
|
+
|
|
334
|
+
// Get device info (if device tracking was enabled)
|
|
335
|
+
const deviceInfo = userData.deviceInfo;
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## 📱 Device Tracking (Optional)
|
|
339
|
+
|
|
340
|
+
Collect device information during onboarding for analytics or support purposes.
|
|
341
|
+
|
|
342
|
+
### Installation
|
|
343
|
+
|
|
344
|
+
First, install the device tracking package:
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
npm install @umituz/react-native-device
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Usage
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
import { OnboardingScreen } from '@umituz/react-native-onboarding';
|
|
354
|
+
|
|
355
|
+
<OnboardingScreen
|
|
356
|
+
slides={slides}
|
|
357
|
+
enableDeviceTracking={true}
|
|
358
|
+
userId="user123" // Optional
|
|
359
|
+
onComplete={async () => {
|
|
360
|
+
const userData = onboardingStore.getUserData();
|
|
361
|
+
|
|
362
|
+
// Device info is now available
|
|
363
|
+
console.log('Device:', userData.deviceInfo?.platform);
|
|
364
|
+
console.log('OS:', userData.deviceInfo?.osVersion);
|
|
365
|
+
console.log('App:', userData.deviceInfo?.appVersion);
|
|
366
|
+
}}
|
|
367
|
+
/>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Device Info Structure
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
{
|
|
374
|
+
deviceId: string;
|
|
375
|
+
platform: 'ios' | 'android' | 'web';
|
|
376
|
+
osVersion: string;
|
|
377
|
+
appVersion: string;
|
|
378
|
+
deviceName: string;
|
|
379
|
+
manufacturer: string;
|
|
380
|
+
brand: string;
|
|
381
|
+
timestamp: number;
|
|
382
|
+
userId?: string; // If provided
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Manual Device Info Collection
|
|
387
|
+
|
|
388
|
+
```tsx
|
|
389
|
+
import { OnboardingDeviceTrackingService } from '@umituz/react-native-onboarding';
|
|
390
|
+
|
|
391
|
+
// Collect device info manually
|
|
392
|
+
const deviceInfo = await OnboardingDeviceTrackingService.collectDeviceInfo('user123');
|
|
393
|
+
|
|
394
|
+
// Check if device tracking is available
|
|
395
|
+
const isAvailable = await OnboardingDeviceTrackingService.isDeviceTrackingAvailable();
|
|
331
396
|
```
|
|
332
397
|
|
|
333
398
|
## 🔄 Resetting Onboarding
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-onboarding",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Advanced onboarding flow for React Native apps with personalization questions, theme-aware colors, animations, and customizable slides. SOLID, DRY, KISS principles applied.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
"react-native-safe-area-context": "^5.0.0",
|
|
45
45
|
"zustand": "^5.0.0"
|
|
46
46
|
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"@umituz/react-native-device": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
47
52
|
"devDependencies": {
|
|
48
53
|
"@types/react": "^18.2.45",
|
|
49
54
|
"@types/react-native": "^0.73.0",
|
|
@@ -85,5 +85,17 @@ export interface OnboardingOptions {
|
|
|
85
85
|
* Show paywall modal on onboarding completion (default: false)
|
|
86
86
|
*/
|
|
87
87
|
showPaywallOnComplete?: boolean;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Enable device tracking (default: false)
|
|
91
|
+
* When enabled, collects device information during onboarding
|
|
92
|
+
*/
|
|
93
|
+
enableDeviceTracking?: boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* User ID for device tracking (optional)
|
|
97
|
+
* Only used when enableDeviceTracking is true
|
|
98
|
+
*/
|
|
99
|
+
userId?: string;
|
|
88
100
|
}
|
|
89
101
|
|
|
@@ -39,5 +39,17 @@ export interface OnboardingUserData {
|
|
|
39
39
|
gender?: string;
|
|
40
40
|
[key: string]: any;
|
|
41
41
|
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Device information (optional)
|
|
45
|
+
* Only collected when enableDeviceTracking is true
|
|
46
|
+
*/
|
|
47
|
+
deviceInfo?: {
|
|
48
|
+
deviceId?: string;
|
|
49
|
+
platform?: string;
|
|
50
|
+
osVersion?: string;
|
|
51
|
+
appVersion?: string;
|
|
52
|
+
[key: string]: any;
|
|
53
|
+
};
|
|
42
54
|
}
|
|
43
55
|
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ export {
|
|
|
52
52
|
useOnboardingNavigation,
|
|
53
53
|
type UseOnboardingNavigationReturn,
|
|
54
54
|
} from "./infrastructure/hooks/useOnboardingNavigation";
|
|
55
|
+
export { OnboardingDeviceTrackingService } from "./infrastructure/services/OnboardingDeviceTrackingService";
|
|
55
56
|
|
|
56
57
|
// =============================================================================
|
|
57
58
|
// PRESENTATION LAYER - Components and Screens
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Onboarding Device Tracking Service
|
|
3
|
+
*
|
|
4
|
+
* Handles device information collection during onboarding
|
|
5
|
+
* Single Responsibility: Device tracking only
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { OnboardingUserData } from "../../domain/entities/OnboardingUserData";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Service for device tracking during onboarding
|
|
12
|
+
* Optional feature - only used when enableDeviceTracking is true
|
|
13
|
+
*/
|
|
14
|
+
export class OnboardingDeviceTrackingService {
|
|
15
|
+
/**
|
|
16
|
+
* Collect device information
|
|
17
|
+
* @param userId - Optional user ID
|
|
18
|
+
* @returns Device information object
|
|
19
|
+
*/
|
|
20
|
+
static async collectDeviceInfo(userId?: string): Promise<OnboardingUserData['deviceInfo']> {
|
|
21
|
+
try {
|
|
22
|
+
// Dynamic import to avoid loading @umituz/react-native-device if not needed
|
|
23
|
+
// @ts-expect-error - Optional peer dependency, may not be installed
|
|
24
|
+
const { DeviceService } = await import('@umituz/react-native-device');
|
|
25
|
+
|
|
26
|
+
const systemInfo = await DeviceService.getSystemInfo({ userId });
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
deviceId: systemInfo.device.modelId || 'unknown',
|
|
30
|
+
platform: systemInfo.device.platform,
|
|
31
|
+
osVersion: systemInfo.device.osVersion || 'unknown',
|
|
32
|
+
appVersion: systemInfo.application.nativeApplicationVersion || 'unknown',
|
|
33
|
+
deviceName: systemInfo.device.deviceName || 'unknown',
|
|
34
|
+
manufacturer: systemInfo.device.manufacturer || 'unknown',
|
|
35
|
+
brand: systemInfo.device.brand || 'unknown',
|
|
36
|
+
timestamp: systemInfo.timestamp,
|
|
37
|
+
...(userId && { userId }),
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
/* eslint-disable-next-line no-console */
|
|
41
|
+
if (__DEV__) console.warn('Failed to collect device info:', error);
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if device tracking is available
|
|
48
|
+
* @returns True if @umituz/react-native-device is available
|
|
49
|
+
*/
|
|
50
|
+
static async isDeviceTrackingAvailable(): Promise<boolean> {
|
|
51
|
+
try {
|
|
52
|
+
// @ts-expect-error - Optional peer dependency, may not be installed
|
|
53
|
+
await import('@umituz/react-native-device');
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
unwrap,
|
|
13
13
|
} from "@umituz/react-native-storage";
|
|
14
14
|
import type { OnboardingUserData } from "../../domain/entities/OnboardingUserData";
|
|
15
|
+
import { OnboardingDeviceTrackingService } from "../services/OnboardingDeviceTrackingService";
|
|
15
16
|
|
|
16
17
|
interface OnboardingStore {
|
|
17
18
|
// State
|
|
@@ -23,7 +24,7 @@ interface OnboardingStore {
|
|
|
23
24
|
|
|
24
25
|
// Actions
|
|
25
26
|
initialize: (storageKey?: string) => Promise<void>;
|
|
26
|
-
complete: (storageKey?: string) => Promise<void>;
|
|
27
|
+
complete: (storageKey?: string, options?: { enableDeviceTracking?: boolean; userId?: string }) => Promise<void>;
|
|
27
28
|
skip: (storageKey?: string) => Promise<void>;
|
|
28
29
|
setCurrentStep: (step: number) => void;
|
|
29
30
|
reset: (storageKey?: string) => Promise<void>;
|
|
@@ -33,6 +34,7 @@ interface OnboardingStore {
|
|
|
33
34
|
getAnswer: (questionId: string) => any;
|
|
34
35
|
getUserData: () => OnboardingUserData;
|
|
35
36
|
setUserData: (data: OnboardingUserData) => Promise<void>;
|
|
37
|
+
collectDeviceInfo: (userId?: string) => Promise<void>;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
const DEFAULT_STORAGE_KEY = StorageKey.ONBOARDING_COMPLETED;
|
|
@@ -67,7 +69,7 @@ export const useOnboardingStore = create<OnboardingStore>((set, get) => ({
|
|
|
67
69
|
});
|
|
68
70
|
},
|
|
69
71
|
|
|
70
|
-
complete: async (storageKey = DEFAULT_STORAGE_KEY) => {
|
|
72
|
+
complete: async (storageKey = DEFAULT_STORAGE_KEY, options?: { enableDeviceTracking?: boolean; userId?: string }) => {
|
|
71
73
|
set({ loading: true, error: null });
|
|
72
74
|
|
|
73
75
|
const result = await storageRepository.setString(storageKey, "true");
|
|
@@ -75,6 +77,12 @@ export const useOnboardingStore = create<OnboardingStore>((set, get) => ({
|
|
|
75
77
|
// Update user data with completion timestamp
|
|
76
78
|
const userData = get().userData;
|
|
77
79
|
userData.completedAt = new Date().toISOString();
|
|
80
|
+
|
|
81
|
+
// Collect device info if enabled
|
|
82
|
+
if (options?.enableDeviceTracking) {
|
|
83
|
+
userData.deviceInfo = await OnboardingDeviceTrackingService.collectDeviceInfo(options.userId);
|
|
84
|
+
}
|
|
85
|
+
|
|
78
86
|
await storageRepository.setObject(USER_DATA_STORAGE_KEY, userData);
|
|
79
87
|
|
|
80
88
|
set({
|
|
@@ -144,6 +152,13 @@ export const useOnboardingStore = create<OnboardingStore>((set, get) => ({
|
|
|
144
152
|
await storageRepository.setObject(USER_DATA_STORAGE_KEY, data);
|
|
145
153
|
set({ userData: data });
|
|
146
154
|
},
|
|
155
|
+
|
|
156
|
+
collectDeviceInfo: async (userId?: string) => {
|
|
157
|
+
const userData = get().userData;
|
|
158
|
+
userData.deviceInfo = await OnboardingDeviceTrackingService.collectDeviceInfo(userId);
|
|
159
|
+
await storageRepository.setObject(USER_DATA_STORAGE_KEY, userData);
|
|
160
|
+
set({ userData: { ...userData } });
|
|
161
|
+
},
|
|
147
162
|
}));
|
|
148
163
|
|
|
149
164
|
/**
|
|
@@ -62,6 +62,18 @@ export interface OnboardingScreenProps extends OnboardingOptions {
|
|
|
62
62
|
* When true, shows premium paywall before completing onboarding
|
|
63
63
|
*/
|
|
64
64
|
showPaywallOnComplete?: boolean;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Enable device tracking (default: false)
|
|
68
|
+
* When enabled, collects device information during onboarding
|
|
69
|
+
*/
|
|
70
|
+
enableDeviceTracking?: boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* User ID for device tracking (optional)
|
|
74
|
+
* Only used when enableDeviceTracking is true
|
|
75
|
+
*/
|
|
76
|
+
userId?: string;
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
/**
|
|
@@ -88,6 +100,8 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
88
100
|
renderSlide,
|
|
89
101
|
onUpgrade,
|
|
90
102
|
showPaywallOnComplete = false,
|
|
103
|
+
enableDeviceTracking = false,
|
|
104
|
+
userId,
|
|
91
105
|
}) => {
|
|
92
106
|
const insets = useSafeAreaInsets();
|
|
93
107
|
const tokens = useAppDesignTokens();
|
|
@@ -112,7 +126,7 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
112
126
|
} = useOnboardingNavigation(
|
|
113
127
|
filteredSlides.length,
|
|
114
128
|
async () => {
|
|
115
|
-
await onboardingStore.complete(storageKey);
|
|
129
|
+
await onboardingStore.complete(storageKey, { enableDeviceTracking, userId });
|
|
116
130
|
if (onComplete) {
|
|
117
131
|
await onComplete();
|
|
118
132
|
}
|
|
@@ -217,16 +231,18 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
|
|
|
217
231
|
useGradient={useGradient}
|
|
218
232
|
/>
|
|
219
233
|
)}
|
|
220
|
-
{
|
|
221
|
-
renderSlide(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
{currentSlide && (
|
|
235
|
+
renderSlide ? (
|
|
236
|
+
renderSlide(currentSlide)
|
|
237
|
+
) : currentSlide.type === "question" && currentSlide.question ? (
|
|
238
|
+
<QuestionSlide
|
|
239
|
+
slide={currentSlide}
|
|
240
|
+
value={currentAnswer}
|
|
241
|
+
onChange={setCurrentAnswer}
|
|
242
|
+
/>
|
|
243
|
+
) : (
|
|
244
|
+
<OnboardingSlideComponent slide={currentSlide} />
|
|
245
|
+
)
|
|
230
246
|
)}
|
|
231
247
|
{renderFooter ? (
|
|
232
248
|
renderFooter({
|