@influto/react-native-sdk 1.0.0 → 1.2.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.
@@ -0,0 +1,397 @@
1
+ "use strict";
2
+ /**
3
+ * ReferralCodeInput - Pre-built UI component for promo code entry
4
+ *
5
+ * Features:
6
+ * - Auto-prefills if user came via attribution link
7
+ * - Real-time validation
8
+ * - Customizable styling (colors, fonts, spacing)
9
+ * - Loading & success/error states
10
+ * - Optional skip button
11
+ * - Callback on validation
12
+ * - Fully accessible
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { ReferralCodeInput } from '@influto/react-native-sdk/ui';
17
+ *
18
+ * <ReferralCodeInput
19
+ * onValidated={(result) => {
20
+ * if (result.valid) {
21
+ * navigation.navigate('Paywall', { campaign: result.campaign });
22
+ * }
23
+ * }}
24
+ * onSkip={() => navigation.navigate('Paywall')}
25
+ * />
26
+ * ```
27
+ */
28
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
29
+ if (k2 === undefined) k2 = k;
30
+ var desc = Object.getOwnPropertyDescriptor(m, k);
31
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
32
+ desc = { enumerable: true, get: function() { return m[k]; } };
33
+ }
34
+ Object.defineProperty(o, k2, desc);
35
+ }) : (function(o, m, k, k2) {
36
+ if (k2 === undefined) k2 = k;
37
+ o[k2] = m[k];
38
+ }));
39
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
40
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
41
+ }) : function(o, v) {
42
+ o["default"] = v;
43
+ });
44
+ var __importStar = (this && this.__importStar) || (function () {
45
+ var ownKeys = function(o) {
46
+ ownKeys = Object.getOwnPropertyNames || function (o) {
47
+ var ar = [];
48
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
49
+ return ar;
50
+ };
51
+ return ownKeys(o);
52
+ };
53
+ return function (mod) {
54
+ if (mod && mod.__esModule) return mod;
55
+ var result = {};
56
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
57
+ __setModuleDefault(result, mod);
58
+ return result;
59
+ };
60
+ })();
61
+ var __importDefault = (this && this.__importDefault) || function (mod) {
62
+ return (mod && mod.__esModule) ? mod : { "default": mod };
63
+ };
64
+ Object.defineProperty(exports, "__esModule", { value: true });
65
+ exports.ReferralCodeInput = void 0;
66
+ const react_1 = __importStar(require("react"));
67
+ const react_native_1 = require("react-native");
68
+ const InfluTo_1 = __importDefault(require("../InfluTo"));
69
+ const ReferralCodeInput = ({ autoPrefill = true, autoValidate = false, onValidated, onSkip, onApplied, appUserId, colors = {}, fonts = {}, labels = {}, style = {}, showSkipButton = true, validateOnBlur = true }) => {
70
+ const [code, setCode] = (0, react_1.useState)('');
71
+ const [state, setState] = (0, react_1.useState)('idle');
72
+ const [validationResult, setValidationResult] = (0, react_1.useState)(null);
73
+ const [isPrefilled, setIsPrefilled] = (0, react_1.useState)(false);
74
+ // Default colors
75
+ const colorScheme = {
76
+ primary: colors.primary || '#3B82F6',
77
+ success: colors.success || '#10B981',
78
+ error: colors.error || '#EF4444',
79
+ text: colors.text || '#1F2937',
80
+ textSecondary: colors.textSecondary || '#6B7280',
81
+ background: colors.background || '#FFFFFF',
82
+ border: colors.border || '#D1D5DB',
83
+ inputBackground: colors.inputBackground || '#F9FAFB'
84
+ };
85
+ // Default fonts
86
+ const fontScheme = {
87
+ family: fonts.family || (react_native_1.Platform.OS === 'ios' ? 'System' : 'Roboto'),
88
+ sizeTitle: fonts.sizeTitle || 18,
89
+ sizeInput: fonts.sizeInput || 16,
90
+ sizeButton: fonts.sizeButton || 16,
91
+ sizeMessage: fonts.sizeMessage || 14
92
+ };
93
+ // Default labels
94
+ const labelScheme = {
95
+ title: labels.title || 'Have a promo code?',
96
+ subtitle: labels.subtitle || 'Enter your referral code to unlock special offers',
97
+ placeholder: labels.placeholder || 'Enter code (e.g., FITGURU30)',
98
+ validateButton: labels.validateButton || 'Apply Code',
99
+ skipButton: labels.skipButton || 'Skip',
100
+ validatingMessage: labels.validatingMessage || 'Validating code...',
101
+ validMessage: labels.validMessage || 'Code applied successfully!',
102
+ invalidMessage: labels.invalidMessage || 'Invalid code. Please try again.',
103
+ errorMessage: labels.errorMessage || 'Unable to validate code. Check your connection.',
104
+ prefilledMessage: labels.prefilledMessage || 'Code detected from your referral link'
105
+ };
106
+ // Auto-prefill on mount
107
+ (0, react_1.useEffect)(() => {
108
+ if (autoPrefill) {
109
+ loadPrefilledCode();
110
+ }
111
+ }, [autoPrefill]);
112
+ const loadPrefilledCode = async () => {
113
+ try {
114
+ const prefilledCode = await InfluTo_1.default.getPrefilledCode();
115
+ if (prefilledCode) {
116
+ setCode(prefilledCode);
117
+ setIsPrefilled(true);
118
+ // Auto-validate if enabled
119
+ if (autoValidate) {
120
+ await handleValidate(prefilledCode);
121
+ }
122
+ }
123
+ }
124
+ catch (error) {
125
+ console.error('[ReferralCodeInput] Failed to load prefilled code:', error);
126
+ }
127
+ };
128
+ const handleValidate = async (codeToValidate = code) => {
129
+ if (!codeToValidate || codeToValidate.length < 4) {
130
+ setState('invalid');
131
+ setValidationResult({
132
+ valid: false,
133
+ error: 'Please enter a valid code',
134
+ error_code: 'INVALID_FORMAT'
135
+ });
136
+ return;
137
+ }
138
+ setState('validating');
139
+ setValidationResult(null);
140
+ try {
141
+ const result = await InfluTo_1.default.validateCode(codeToValidate);
142
+ setValidationResult(result);
143
+ if (result.valid) {
144
+ setState('valid');
145
+ // Automatically set the code if valid
146
+ const setResult = await InfluTo_1.default.setReferralCode(codeToValidate, appUserId);
147
+ if (setResult.success) {
148
+ onApplied?.(result);
149
+ }
150
+ }
151
+ else {
152
+ setState('invalid');
153
+ }
154
+ // Notify parent
155
+ onValidated?.(result);
156
+ }
157
+ catch (error) {
158
+ setState('error');
159
+ setValidationResult({
160
+ valid: false,
161
+ error: 'Network error',
162
+ error_code: 'NETWORK_ERROR'
163
+ });
164
+ onValidated?.({
165
+ valid: false,
166
+ error: 'Network error',
167
+ error_code: 'NETWORK_ERROR'
168
+ });
169
+ }
170
+ };
171
+ const handleSkip = () => {
172
+ onSkip?.();
173
+ };
174
+ const handleBlur = () => {
175
+ if (validateOnBlur && code && state === 'idle') {
176
+ handleValidate();
177
+ }
178
+ };
179
+ // Get message based on state
180
+ const getMessage = () => {
181
+ if (isPrefilled && state === 'idle') {
182
+ return { text: labelScheme.prefilledMessage, color: colorScheme.primary };
183
+ }
184
+ switch (state) {
185
+ case 'validating':
186
+ return { text: labelScheme.validatingMessage, color: colorScheme.textSecondary };
187
+ case 'valid':
188
+ return {
189
+ text: validationResult?.message || labelScheme.validMessage,
190
+ color: colorScheme.success
191
+ };
192
+ case 'invalid':
193
+ return {
194
+ text: validationResult?.error || labelScheme.invalidMessage,
195
+ color: colorScheme.error
196
+ };
197
+ case 'error':
198
+ return { text: labelScheme.errorMessage, color: colorScheme.error };
199
+ default:
200
+ return null;
201
+ }
202
+ };
203
+ const message = getMessage();
204
+ return (react_1.default.createElement(react_native_1.View, { style: [styles.container, style.container] },
205
+ react_1.default.createElement(react_native_1.View, { style: [styles.titleContainer, style.titleContainer] },
206
+ react_1.default.createElement(react_native_1.Text, { style: [
207
+ styles.title,
208
+ { color: colorScheme.text, fontFamily: fontScheme.family, fontSize: fontScheme.sizeTitle },
209
+ style.title
210
+ ] }, labelScheme.title),
211
+ labelScheme.subtitle && (react_1.default.createElement(react_native_1.Text, { style: [
212
+ styles.subtitle,
213
+ { color: colorScheme.textSecondary, fontFamily: fontScheme.family, fontSize: fontScheme.sizeMessage },
214
+ style.subtitle
215
+ ] }, labelScheme.subtitle))),
216
+ react_1.default.createElement(react_native_1.View, { style: [styles.inputContainer, style.inputContainer] },
217
+ react_1.default.createElement(react_native_1.TextInput, { style: [
218
+ styles.input,
219
+ {
220
+ backgroundColor: colorScheme.inputBackground,
221
+ borderColor: state === 'valid' ? colorScheme.success : state === 'invalid' || state === 'error' ? colorScheme.error : colorScheme.border,
222
+ color: colorScheme.text,
223
+ fontFamily: fontScheme.family,
224
+ fontSize: fontScheme.sizeInput
225
+ },
226
+ style.input
227
+ ], value: code, onChangeText: (text) => {
228
+ setCode(text.toUpperCase());
229
+ if (state !== 'idle' && state !== 'validating') {
230
+ setState('idle');
231
+ setValidationResult(null);
232
+ }
233
+ }, onBlur: handleBlur, placeholder: labelScheme.placeholder, placeholderTextColor: colorScheme.textSecondary, autoCapitalize: "characters", autoCorrect: false, autoComplete: "off", maxLength: 20, editable: state !== 'validating', accessibilityLabel: "Referral code input", accessibilityHint: "Enter your promo or referral code" }),
234
+ state === 'valid' && (react_1.default.createElement(react_native_1.View, { style: styles.iconContainer },
235
+ react_1.default.createElement(react_native_1.Text, { style: [styles.icon, { color: colorScheme.success }] }, "\u2713"))),
236
+ (state === 'invalid' || state === 'error') && (react_1.default.createElement(react_native_1.View, { style: styles.iconContainer },
237
+ react_1.default.createElement(react_native_1.Text, { style: [styles.icon, { color: colorScheme.error }] }, "\u2717")))),
238
+ message && (react_1.default.createElement(react_native_1.View, { style: [styles.messageContainer, style.messageContainer] },
239
+ react_1.default.createElement(react_native_1.Text, { style: [
240
+ styles.messageText,
241
+ { color: message.color, fontFamily: fontScheme.family, fontSize: fontScheme.sizeMessage },
242
+ style.messageText
243
+ ] }, message.text))),
244
+ react_1.default.createElement(react_native_1.View, { style: [styles.buttonContainer, style.buttonContainer] },
245
+ react_1.default.createElement(react_native_1.TouchableOpacity, { style: [
246
+ styles.validateButton,
247
+ {
248
+ backgroundColor: state === 'valid' ? colorScheme.success : colorScheme.primary,
249
+ opacity: state === 'validating' || !code ? 0.6 : 1
250
+ },
251
+ style.validateButton
252
+ ], onPress: () => handleValidate(), disabled: state === 'validating' || !code, accessibilityLabel: labelScheme.validateButton, accessibilityRole: "button" }, state === 'validating' ? (react_1.default.createElement(react_native_1.ActivityIndicator, { color: "#FFFFFF", size: "small" })) : (react_1.default.createElement(react_native_1.Text, { style: [
253
+ styles.validateButtonText,
254
+ { fontFamily: fontScheme.family, fontSize: fontScheme.sizeButton },
255
+ style.validateButtonText
256
+ ] },
257
+ state === 'valid' ? '✓ ' : '',
258
+ labelScheme.validateButton))),
259
+ showSkipButton && onSkip && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.skipButton, style.skipButton], onPress: handleSkip, disabled: state === 'validating', accessibilityLabel: labelScheme.skipButton, accessibilityRole: "button" },
260
+ react_1.default.createElement(react_native_1.Text, { style: [
261
+ styles.skipButtonText,
262
+ { color: colorScheme.textSecondary, fontFamily: fontScheme.family, fontSize: fontScheme.sizeButton },
263
+ style.skipButtonText
264
+ ] }, labelScheme.skipButton)))),
265
+ state === 'valid' && validationResult?.campaign && (react_1.default.createElement(react_native_1.View, { style: styles.campaignInfo },
266
+ react_1.default.createElement(react_native_1.Text, { style: [
267
+ styles.campaignTitle,
268
+ { color: colorScheme.text, fontFamily: fontScheme.family }
269
+ ] }, validationResult.campaign.name),
270
+ validationResult.campaign.description && (react_1.default.createElement(react_native_1.Text, { style: [
271
+ styles.campaignDescription,
272
+ { color: colorScheme.textSecondary, fontFamily: fontScheme.family }
273
+ ] }, validationResult.campaign.description)),
274
+ validationResult.influencer && (react_1.default.createElement(react_native_1.Text, { style: [
275
+ styles.influencerInfo,
276
+ { color: colorScheme.textSecondary, fontFamily: fontScheme.family }
277
+ ] },
278
+ "Referred by ",
279
+ validationResult.influencer.name,
280
+ validationResult.influencer.social_handle && ` (@${validationResult.influencer.social_handle})`))))));
281
+ };
282
+ exports.ReferralCodeInput = ReferralCodeInput;
283
+ const styles = react_native_1.StyleSheet.create({
284
+ container: {
285
+ padding: 20,
286
+ backgroundColor: '#FFFFFF',
287
+ borderRadius: 12,
288
+ shadowColor: '#000',
289
+ shadowOffset: { width: 0, height: 2 },
290
+ shadowOpacity: 0.1,
291
+ shadowRadius: 8,
292
+ elevation: 4
293
+ },
294
+ titleContainer: {
295
+ marginBottom: 16
296
+ },
297
+ title: {
298
+ fontSize: 18,
299
+ fontWeight: '600',
300
+ marginBottom: 4
301
+ },
302
+ subtitle: {
303
+ fontSize: 14,
304
+ lineHeight: 20
305
+ },
306
+ inputContainer: {
307
+ position: 'relative',
308
+ marginBottom: 12
309
+ },
310
+ input: {
311
+ height: 50,
312
+ borderWidth: 2,
313
+ borderRadius: 8,
314
+ paddingHorizontal: 16,
315
+ paddingRight: 48, // Space for icon
316
+ fontSize: 16,
317
+ fontWeight: '500'
318
+ },
319
+ iconContainer: {
320
+ position: 'absolute',
321
+ right: 16,
322
+ top: 12,
323
+ width: 24,
324
+ height: 24,
325
+ justifyContent: 'center',
326
+ alignItems: 'center'
327
+ },
328
+ icon: {
329
+ fontSize: 20,
330
+ fontWeight: 'bold'
331
+ },
332
+ messageContainer: {
333
+ marginBottom: 12,
334
+ padding: 12,
335
+ backgroundColor: '#F9FAFB',
336
+ borderRadius: 6
337
+ },
338
+ messageText: {
339
+ fontSize: 14,
340
+ lineHeight: 18,
341
+ textAlign: 'center'
342
+ },
343
+ buttonContainer: {
344
+ flexDirection: 'row',
345
+ gap: 12
346
+ },
347
+ validateButton: {
348
+ flex: 1,
349
+ height: 50,
350
+ borderRadius: 8,
351
+ justifyContent: 'center',
352
+ alignItems: 'center',
353
+ shadowColor: '#000',
354
+ shadowOffset: { width: 0, height: 2 },
355
+ shadowOpacity: 0.1,
356
+ shadowRadius: 4,
357
+ elevation: 2
358
+ },
359
+ validateButtonText: {
360
+ color: '#FFFFFF',
361
+ fontSize: 16,
362
+ fontWeight: '600'
363
+ },
364
+ skipButton: {
365
+ height: 50,
366
+ paddingHorizontal: 20,
367
+ justifyContent: 'center',
368
+ alignItems: 'center'
369
+ },
370
+ skipButtonText: {
371
+ fontSize: 16,
372
+ fontWeight: '500'
373
+ },
374
+ campaignInfo: {
375
+ marginTop: 16,
376
+ padding: 12,
377
+ backgroundColor: '#F0FDF4',
378
+ borderRadius: 8,
379
+ borderLeftWidth: 3,
380
+ borderLeftColor: '#10B981'
381
+ },
382
+ campaignTitle: {
383
+ fontSize: 15,
384
+ fontWeight: '600',
385
+ marginBottom: 4
386
+ },
387
+ campaignDescription: {
388
+ fontSize: 13,
389
+ lineHeight: 18,
390
+ marginBottom: 4
391
+ },
392
+ influencerInfo: {
393
+ fontSize: 12,
394
+ fontStyle: 'italic'
395
+ }
396
+ });
397
+ exports.default = exports.ReferralCodeInput;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * InfluTo SDK UI Components
3
+ *
4
+ * Pre-built React Native components for easy integration
5
+ */
6
+ export { ReferralCodeInput } from './ReferralCodeInput';
7
+ export type { ReferralCodeInputProps } from './ReferralCodeInput';
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * InfluTo SDK UI Components
4
+ *
5
+ * Pre-built React Native components for easy integration
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.ReferralCodeInput = void 0;
9
+ var ReferralCodeInput_1 = require("./ReferralCodeInput");
10
+ Object.defineProperty(exports, "ReferralCodeInput", { enumerable: true, get: function () { return ReferralCodeInput_1.ReferralCodeInput; } });
package/lib/index.js CHANGED
@@ -25,3 +25,5 @@ exports.default = void 0;
25
25
  var InfluTo_1 = require("./InfluTo");
26
26
  Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(InfluTo_1).default; } });
27
27
  __exportStar(require("./types"), exports);
28
+ // Note: UI components are exported separately via './ui' to keep bundle size small
29
+ // Import with: import { ReferralCodeInput } from '@influto/react-native-sdk/ui';
package/lib/types.d.ts CHANGED
@@ -74,3 +74,68 @@ export interface DeviceInfo {
74
74
  timezone?: string;
75
75
  language?: string;
76
76
  }
77
+ export interface CodeValidationResult {
78
+ /**
79
+ * Whether the code is valid
80
+ */
81
+ valid: boolean;
82
+ /**
83
+ * Normalized code (uppercase)
84
+ */
85
+ code?: string;
86
+ /**
87
+ * Campaign information if valid
88
+ */
89
+ campaign?: {
90
+ id: string;
91
+ name: string;
92
+ description?: string;
93
+ commission_percentage: number;
94
+ campaign_type: string;
95
+ };
96
+ /**
97
+ * Influencer information if available
98
+ */
99
+ influencer?: {
100
+ name: string;
101
+ social_handle?: string;
102
+ follower_count?: number;
103
+ };
104
+ /**
105
+ * Custom campaign data (for conditional offers)
106
+ */
107
+ custom_data?: Record<string, any>;
108
+ /**
109
+ * Success message
110
+ */
111
+ message?: string;
112
+ /**
113
+ * Error message if invalid
114
+ */
115
+ error?: string;
116
+ /**
117
+ * Error code for programmatic handling
118
+ */
119
+ error_code?: 'INVALID_FORMAT' | 'CODE_NOT_FOUND' | 'NETWORK_ERROR';
120
+ }
121
+ export interface SetCodeResult {
122
+ /**
123
+ * Whether code was set successfully
124
+ */
125
+ success: boolean;
126
+ /**
127
+ * Normalized code
128
+ */
129
+ code?: string;
130
+ /**
131
+ * Success/error message
132
+ */
133
+ message: string;
134
+ /**
135
+ * Campaign info
136
+ */
137
+ campaign?: {
138
+ id: string;
139
+ name: string;
140
+ };
141
+ }
package/lib/ui.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * InfluTo SDK - UI Components Entry Point
3
+ *
4
+ * Import UI components separately to keep main SDK bundle small.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { ReferralCodeInput } from '@influto/react-native-sdk/ui';
9
+ * ```
10
+ */
11
+ export * from './components';
package/lib/ui.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /**
3
+ * InfluTo SDK - UI Components Entry Point
4
+ *
5
+ * Import UI components separately to keep main SDK bundle small.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { ReferralCodeInput } from '@influto/react-native-sdk/ui';
10
+ * ```
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
24
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
25
+ };
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ __exportStar(require("./components"), exports);
package/package.json CHANGED
@@ -1,9 +1,21 @@
1
1
  {
2
2
  "name": "@influto/react-native-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "InfluTo SDK for React Native - Track influencer referrals and conversions",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./lib/index.js",
10
+ "require": "./lib/index.js",
11
+ "types": "./lib/index.d.ts"
12
+ },
13
+ "./ui": {
14
+ "import": "./lib/ui.js",
15
+ "require": "./lib/ui.js",
16
+ "types": "./lib/ui.d.ts"
17
+ }
18
+ },
7
19
  "files": [
8
20
  "lib",
9
21
  "src",