noboarding 0.1.0-alpha

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 ADDED
@@ -0,0 +1,515 @@
1
+ # Noboarding - React Native SDK
2
+
3
+ React Native SDK for rendering server-driven onboarding flows. Integrate once, then update your onboarding screens remotely from the dashboard — no App Store reviews needed.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install noboarding
9
+ # or
10
+ yarn add noboarding
11
+ ```
12
+
13
+ **📚 Complete Setup Guides:**
14
+ - **[AI Setup](./SETUP_GUIDE.md#ai-setup)** - Copy/paste instructions for your AI coding assistant (Claude Code, Cursor, etc.)
15
+ - **[Manual Setup](./SETUP_GUIDE.md#normal-setup)** - Step-by-step instructions
16
+ - **[RevenueCat Integration](./REVENUECAT_SETUP.md)** - Detailed RevenueCat paywall guide
17
+
18
+ ## Quick Start
19
+
20
+ ```typescript
21
+ import { OnboardingFlow } from 'noboarding';
22
+
23
+ function App() {
24
+ const [showOnboarding, setShowOnboarding] = useState(true);
25
+
26
+ if (showOnboarding) {
27
+ return (
28
+ <OnboardingFlow
29
+ apiKey="sk_live_your_api_key_here"
30
+ onComplete={(userData) => {
31
+ console.log('Collected data:', userData);
32
+ setShowOnboarding(false);
33
+ }}
34
+ onSkip={() => {
35
+ setShowOnboarding(false);
36
+ }}
37
+ // Optional: Get the generated user ID to sync with other services
38
+ onUserIdGenerated={(userId) => {
39
+ console.log('User ID:', userId);
40
+ // Use this to sync with RevenueCat, analytics, etc.
41
+ }}
42
+ />
43
+ );
44
+ }
45
+
46
+ return <YourMainApp />;
47
+ }
48
+ ```
49
+
50
+ ## How It Works
51
+
52
+ 1. The SDK fetches your onboarding configuration from Supabase at runtime
53
+ 2. Screens defined as JSON element trees are rendered natively using `ElementRenderer`
54
+ 3. You update screens in the dashboard, publish, and the SDK picks up changes automatically
55
+ 4. No app binary changes required — everything is data-driven
56
+
57
+ ## Screen Types
58
+
59
+ ### Custom Screen (AI-generated)
60
+
61
+ Screens built with the composable primitive system. The `ElementRenderer` recursively maps a JSON element tree to native React Native components (`View`, `Text`, `Image`, `ScrollView`, `TextInput`, `TouchableOpacity`).
62
+
63
+ ```typescript
64
+ // A custom screen in your config looks like:
65
+ {
66
+ "id": "welcome",
67
+ "type": "custom_screen",
68
+ "props": {},
69
+ "elements": [
70
+ {
71
+ "id": "root",
72
+ "type": "vstack",
73
+ "style": { "width": "100%", "height": "100%", "padding": 24 },
74
+ "children": [
75
+ { "id": "title", "type": "text", "props": { "text": "Welcome!" }, "style": { "fontSize": 32, "fontWeight": "700" } },
76
+ { "id": "spacer", "type": "spacer" },
77
+ {
78
+ "id": "cta",
79
+ "type": "hstack",
80
+ "style": { "backgroundColor": "#000", "borderRadius": 12, "padding": 16, "justifyContent": "center" },
81
+ "children": [
82
+ { "id": "cta_text", "type": "text", "props": { "text": "Get Started" }, "style": { "color": "#fff", "fontSize": 16 } }
83
+ ],
84
+ "action": { "type": "navigate", "destination": "next" }
85
+ }
86
+ ]
87
+ }
88
+ ]
89
+ }
90
+ ```
91
+
92
+ ### Pre-built Components
93
+
94
+ - **WelcomeScreen** — Image + title + subtitle + CTA button
95
+ - **TextInput** — Form for collecting user data (name, email, etc.)
96
+ - **SocialLogin** — Apple/Google/Facebook authentication buttons
97
+
98
+ ## Composable Primitives
99
+
100
+ The element tree uses these building blocks:
101
+
102
+ **Containers** (have `children` array):
103
+ - `vstack` — vertical flex column
104
+ - `hstack` — horizontal flex row
105
+ - `zstack` — layered/overlapping elements
106
+ - `scrollview` — scrollable container
107
+
108
+ **Content** (leaf elements with `props`):
109
+ - `text` — text content (`props.text`)
110
+ - `image` — image (`props.url`, `props.slotNumber`)
111
+ - `video` — video placeholder (`props.url`)
112
+ - `lottie` — Lottie animation (`props.url`)
113
+ - `icon` — emoji (`props.emoji`) or named icon (`props.name`, `props.library`)
114
+ - `input` — text field (`props.placeholder`, `props.type`)
115
+ - `spacer` — flexible empty space
116
+ - `divider` — horizontal line
117
+
118
+ ## Actions
119
+
120
+ Any container can have an `action` to make it interactive:
121
+
122
+ ```typescript
123
+ action: {
124
+ type: 'tap' | 'navigate' | 'link' | 'toggle' | 'dismiss',
125
+ destination?: string // URL for link, screen ID for navigate
126
+ }
127
+ ```
128
+
129
+ | Action | Behavior |
130
+ |--------|----------|
131
+ | `tap` | Generic tap handler |
132
+ | `navigate` | Go to `"next"`, `"previous"`, or a specific screen ID |
133
+ | `link` | Open URL via `Linking.openURL` |
134
+ | `toggle` | Toggle selected/unselected state (visual border change) |
135
+ | `dismiss` | Dismiss current screen or flow |
136
+
137
+ ## Custom Screens (Developer-Registered Components)
138
+
139
+ For advanced use cases requiring native features (camera, payments, biometrics) or third-party SDKs, you can create custom React Native components and register them with the SDK.
140
+
141
+ ### Creating a Custom Screen
142
+
143
+ 1. **Create your component** with the required props:
144
+
145
+ ```typescript
146
+ // screens/PaywallScreen.tsx
147
+ import React, { useEffect } from 'react';
148
+ import { View, Text, Button } from 'react-native';
149
+ import type { CustomScreenProps } from 'noboarding';
150
+
151
+ export const PaywallScreen: React.FC<CustomScreenProps> = ({
152
+ analytics,
153
+ onNext,
154
+ onSkip,
155
+ preview,
156
+ data,
157
+ onDataUpdate,
158
+ }) => {
159
+ useEffect(() => {
160
+ analytics.track('paywall_viewed', {
161
+ screen_id: 'paywall',
162
+ });
163
+ }, []);
164
+
165
+ const handlePurchase = () => {
166
+ analytics.track('paywall_conversion', {
167
+ package: 'premium_monthly',
168
+ });
169
+
170
+ onDataUpdate?.({
171
+ premium: true,
172
+ purchase_date: new Date().toISOString(),
173
+ });
174
+
175
+ onNext();
176
+ };
177
+
178
+ // Preview mode for dashboard
179
+ if (preview) {
180
+ return (
181
+ <View style={{ padding: 20, alignItems: 'center' }}>
182
+ <Text style={{ fontSize: 64 }}>💎</Text>
183
+ <Text style={{ fontSize: 24, fontWeight: 'bold', marginVertical: 20 }}>
184
+ Paywall Preview
185
+ </Text>
186
+ <Text style={{ color: '#666', marginBottom: 20 }}>
187
+ (Real paywall only works in app)
188
+ </Text>
189
+ <Button title="Continue" onPress={onNext} />
190
+ </View>
191
+ );
192
+ }
193
+
194
+ return (
195
+ <View style={{ flex: 1, padding: 20 }}>
196
+ <Text style={{ fontSize: 28, fontWeight: 'bold', marginBottom: 20 }}>
197
+ Unlock Premium
198
+ </Text>
199
+ <Button title="Subscribe - $9.99/month" onPress={handlePurchase} />
200
+ {onSkip && (
201
+ <Button title="Maybe Later" onPress={onSkip} color="#666" />
202
+ )}
203
+ </View>
204
+ );
205
+ };
206
+ ```
207
+
208
+ 2. **Register the component** in your app:
209
+
210
+ ```typescript
211
+ import { OnboardingFlow } from 'noboarding';
212
+ import { PaywallScreen } from './screens/PaywallScreen';
213
+
214
+ function App() {
215
+ return (
216
+ <OnboardingFlow
217
+ apiKey="sk_live_your_api_key_here"
218
+ customComponents={{
219
+ PaywallScreen: PaywallScreen, // Register here
220
+ }}
221
+ onComplete={(userData) => {
222
+ console.log('User data:', userData);
223
+ // userData includes data from custom screens
224
+ }}
225
+ />
226
+ );
227
+ }
228
+ ```
229
+
230
+ 3. **Add to your flow** in the dashboard:
231
+ - Click "Add Custom Screen"
232
+ - Enter component name: `PaywallScreen`
233
+ - Position in flow
234
+
235
+ ### CustomScreenProps Interface
236
+
237
+ ```typescript
238
+ interface CustomScreenProps {
239
+ analytics: {
240
+ track: (event: string, properties?: Record<string, any>) => void;
241
+ };
242
+ onNext: () => void;
243
+ onSkip?: () => void;
244
+ preview?: boolean; // True when rendering in dashboard preview
245
+ data?: Record<string, any>; // Previously collected user data
246
+ onDataUpdate?: (data: Record<string, any>) => void; // Update collected data
247
+ }
248
+ ```
249
+
250
+ ### RevenueCat Integration Example
251
+
252
+ Here's a complete example integrating RevenueCat paywalls:
253
+
254
+ ```typescript
255
+ // screens/RevenueCatPaywall.tsx
256
+ import React, { useState, useEffect } from 'react';
257
+ import { View, Text, ActivityIndicator, Alert } from 'react-native';
258
+ import Purchases, { PurchasesOffering } from 'react-native-purchases';
259
+ import type { CustomScreenProps } from 'noboarding';
260
+
261
+ export const RevenueCatPaywall: React.FC<CustomScreenProps> = ({
262
+ analytics,
263
+ onNext,
264
+ onSkip,
265
+ preview,
266
+ onDataUpdate,
267
+ }) => {
268
+ const [offering, setOffering] = useState<PurchasesOffering | null>(null);
269
+ const [loading, setLoading] = useState(true);
270
+
271
+ useEffect(() => {
272
+ analytics.track('paywall_viewed');
273
+ loadOffering();
274
+ }, []);
275
+
276
+ const loadOffering = async () => {
277
+ try {
278
+ const offerings = await Purchases.getOfferings();
279
+ setOffering(offerings.current);
280
+
281
+ analytics.track('paywall_loaded', {
282
+ offering_id: offerings.current?.identifier,
283
+ packages_count: offerings.current?.availablePackages.length,
284
+ });
285
+ } catch (error: any) {
286
+ analytics.track('paywall_error', { error: error.message });
287
+ } finally {
288
+ setLoading(false);
289
+ }
290
+ };
291
+
292
+ const handlePurchase = async (packageId: string) => {
293
+ try {
294
+ analytics.track('paywall_purchase_started', { package: packageId });
295
+
296
+ const pkg = offering?.availablePackages.find(p => p.identifier === packageId);
297
+ if (!pkg) return;
298
+
299
+ const { customerInfo } = await Purchases.purchasePackage(pkg);
300
+ const isPremium = customerInfo.entitlements.active['premium'] !== undefined;
301
+
302
+ if (isPremium) {
303
+ analytics.track('paywall_conversion', {
304
+ package: packageId,
305
+ price: pkg.product.priceString,
306
+ });
307
+
308
+ onDataUpdate?.({
309
+ premium: true,
310
+ package: packageId,
311
+ purchase_timestamp: new Date().toISOString(),
312
+ });
313
+
314
+ Alert.alert('Welcome to Premium!', '', [
315
+ { text: 'Continue', onPress: onNext }
316
+ ]);
317
+ }
318
+ } catch (error: any) {
319
+ const cancelled = error.userCancelled;
320
+
321
+ analytics.track('paywall_purchase_failed', {
322
+ package: packageId,
323
+ cancelled,
324
+ });
325
+
326
+ if (!cancelled) {
327
+ Alert.alert('Purchase Failed', 'Please try again.');
328
+ }
329
+ }
330
+ };
331
+
332
+ const handleSkip = () => {
333
+ analytics.track('paywall_dismissed');
334
+ onSkip?.() || onNext();
335
+ };
336
+
337
+ // Preview mode
338
+ if (preview) {
339
+ return (
340
+ <View style={{ padding: 20, alignItems: 'center' }}>
341
+ <Text style={{ fontSize: 64, marginBottom: 20 }}>💎</Text>
342
+ <Text style={{ fontSize: 24, fontWeight: 'bold' }}>
343
+ RevenueCat Paywall
344
+ </Text>
345
+ <Text style={{ color: '#999', marginTop: 8 }}>
346
+ (Preview - real paywall in app)
347
+ </Text>
348
+ </View>
349
+ );
350
+ }
351
+
352
+ if (loading) {
353
+ return (
354
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
355
+ <ActivityIndicator size="large" />
356
+ </View>
357
+ );
358
+ }
359
+
360
+ return (
361
+ <View style={{ flex: 1, padding: 20 }}>
362
+ <Text style={{ fontSize: 32, fontWeight: 'bold', marginBottom: 20 }}>
363
+ Unlock Premium
364
+ </Text>
365
+
366
+ {offering?.availablePackages.map(pkg => (
367
+ <TouchableOpacity
368
+ key={pkg.identifier}
369
+ onPress={() => handlePurchase(pkg.identifier)}
370
+ style={{
371
+ backgroundColor: '#007AFF',
372
+ padding: 16,
373
+ borderRadius: 12,
374
+ marginBottom: 12,
375
+ }}
376
+ >
377
+ <Text style={{ color: '#FFF', fontSize: 18, fontWeight: 'bold' }}>
378
+ {pkg.product.title} - {pkg.product.priceString}
379
+ </Text>
380
+ </TouchableOpacity>
381
+ ))}
382
+
383
+ <Button title="Maybe Later" onPress={handleSkip} color="#666" />
384
+ </View>
385
+ );
386
+ };
387
+ ```
388
+
389
+ **Setup:**
390
+
391
+ 1. Install RevenueCat:
392
+ ```bash
393
+ npm install react-native-purchases
394
+ ```
395
+
396
+ 2. Configure RevenueCat in your app initialization:
397
+ ```typescript
398
+ // App.tsx
399
+ import Purchases from 'react-native-purchases';
400
+
401
+ useEffect(() => {
402
+ Purchases.configure({
403
+ apiKey: Platform.OS === 'ios'
404
+ ? 'appl_YOUR_KEY'
405
+ : 'goog_YOUR_KEY',
406
+ });
407
+ }, []);
408
+ ```
409
+
410
+ 3. Register and use in onboarding:
411
+ ```typescript
412
+ <OnboardingFlow
413
+ customComponents={{
414
+ RevenueCatPaywall: RevenueCatPaywall,
415
+ }}
416
+ />
417
+ ```
418
+
419
+ 4. Set up webhooks (see below) to track conversions server-side
420
+
421
+ ### Best Practices
422
+
423
+ - ✅ Always implement `preview` mode for dashboard compatibility
424
+ - ✅ Track key events with `analytics.track()`
425
+ - ✅ Use `onDataUpdate()` to save data from custom screens
426
+ - ✅ Handle errors gracefully with user-friendly messages
427
+ - ✅ Call `onNext()` when screen is complete
428
+
429
+ For more details, see [Custom Screens Guide](./cusomte_screens.md).
430
+
431
+ ## API
432
+
433
+ ### OnboardingFlow Props
434
+
435
+ | Prop | Type | Required | Description |
436
+ |------|------|----------|-------------|
437
+ | `apiKey` | `string` | Yes | Your API key from the dashboard |
438
+ | `onComplete` | `(data?) => void` | Yes | Called when user completes onboarding |
439
+ | `onSkip` | `() => void` | No | Called when user skips onboarding |
440
+ | `baseUrl` | `string` | No | Custom API base URL |
441
+ | `customComponents` | `Record<string, Component>` | No | Developer-registered custom screen components |
442
+ | `initialVariables` | `Record<string, any>` | No | Initial values for the variable store |
443
+ | `onUserIdGenerated` | `(userId: string) => void` | No | Called when SDK generates user ID (use to sync with RevenueCat, analytics, etc.) |
444
+
445
+ ### ElementRenderer Props
446
+
447
+ | Prop | Type | Required | Description |
448
+ |------|------|----------|-------------|
449
+ | `elements` | `ElementNode[]` | Yes | The element tree to render |
450
+ | `analytics` | `AnalyticsManager` | Yes | Analytics manager for tracking |
451
+ | `screenId` | `string` | Yes | Current screen ID for analytics |
452
+ | `onNavigate` | `(destination: string) => void` | Yes | Navigation handler |
453
+ | `onDismiss` | `() => void` | Yes | Dismiss handler |
454
+
455
+ ## Auto-Tracked Events
456
+
457
+ The SDK automatically tracks:
458
+
459
+ - `onboarding_started`
460
+ - `screen_viewed`
461
+ - `screen_completed`
462
+ - `screen_skipped`
463
+ - `time_on_screen`
464
+ - `button_clicked`
465
+ - `input_focused`
466
+ - `input_completed`
467
+ - `onboarding_completed`
468
+ - `onboarding_abandoned`
469
+ - `element_action` — tracks every action with element ID, action type, and screen ID
470
+
471
+ ## Exports
472
+
473
+ ```typescript
474
+ // Main component
475
+ import { OnboardingFlow } from 'noboarding';
476
+
477
+ // Element renderer (for custom usage)
478
+ import { ElementRenderer } from 'noboarding';
479
+
480
+ // Types
481
+ import type {
482
+ OnboardingFlowProps,
483
+ ScreenConfig,
484
+ OnboardingConfig,
485
+ ElementNode,
486
+ ElementType,
487
+ ElementAction,
488
+ ElementStyle,
489
+ ElementPosition,
490
+ AnalyticsEvent,
491
+ } from 'noboarding';
492
+
493
+ // Utilities
494
+ import { API, AnalyticsManager } from 'noboarding';
495
+ ```
496
+
497
+ ## Development
498
+
499
+ The TestApp imports the SDK from the compiled `lib/` directory (`"main": "lib/index.js"`), not from `src/` directly. After making any changes to files in `sdk/src/`, you must rebuild before testing:
500
+
501
+ ```bash
502
+ cd sdk
503
+ npm run build
504
+ ```
505
+
506
+ Then restart the TestApp. If you skip this step, the TestApp will still be running the old compiled code and your changes won't take effect.
507
+
508
+ ## Requirements
509
+
510
+ - React Native >= 0.60.0
511
+ - React >= 16.8.0
512
+
513
+ ## License
514
+
515
+ MIT