react-native-inapp-inspector 2.2.2 → 2.2.3

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 (97) hide show
  1. package/dist/commonjs/constants/version.d.ts +1 -1
  2. package/dist/commonjs/constants/version.js +1 -1
  3. package/dist/esm/constants/version.d.ts +1 -1
  4. package/dist/esm/constants/version.js +1 -1
  5. package/package.json +2 -1
  6. package/src/analytics.ts +35 -0
  7. package/src/bundle.ts +25 -0
  8. package/src/components/AnalyticsDetail.tsx +865 -0
  9. package/src/components/AnalyticsEventCard.tsx +441 -0
  10. package/src/components/AnalyticsGraph.tsx +583 -0
  11. package/src/components/AnimatedEntrance.tsx +64 -0
  12. package/src/components/AppHeaderLogo.tsx +109 -0
  13. package/src/components/BrandCircleIcon.tsx +144 -0
  14. package/src/components/BrandSquareIcon.tsx +144 -0
  15. package/src/components/CodeSnippet.tsx +725 -0
  16. package/src/components/ConsoleLogCard.tsx +628 -0
  17. package/src/components/CopyButton.tsx +87 -0
  18. package/src/components/DiffViewer.tsx +82 -0
  19. package/src/components/DomainHeader.tsx +237 -0
  20. package/src/components/EmptyState.tsx +73 -0
  21. package/src/components/EndOfListFooter.tsx +100 -0
  22. package/src/components/ErrorBoundary.tsx +688 -0
  23. package/src/components/HeadersSection.tsx +192 -0
  24. package/src/components/HighlightText.tsx +100 -0
  25. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  26. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  27. package/src/components/Inspector/BundleTab.tsx +4731 -0
  28. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  29. package/src/components/Inspector/CrashDetail.tsx +941 -0
  30. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  31. package/src/components/Inspector/CrashTab.tsx +871 -0
  32. package/src/components/Inspector/FabLauncher.tsx +80 -0
  33. package/src/components/Inspector/InspectorContext.tsx +28 -0
  34. package/src/components/Inspector/InspectorHeader.tsx +973 -0
  35. package/src/components/Inspector/LogDetail.tsx +1427 -0
  36. package/src/components/Inspector/MainScreen.tsx +258 -0
  37. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  38. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  39. package/src/components/Inspector/NetworkTab.tsx +1095 -0
  40. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  41. package/src/components/Inspector/PerformanceTab.tsx +1894 -0
  42. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  43. package/src/components/Inspector/ReduxTab.tsx +954 -0
  44. package/src/components/Inspector/SettingsPanel.tsx +3181 -0
  45. package/src/components/Inspector/TabBar.tsx +187 -0
  46. package/src/components/Inspector/TelemetryConsentModal.tsx +392 -0
  47. package/src/components/Inspector/UpdateAvailableModal.tsx +545 -0
  48. package/src/components/JsonViewer.tsx +486 -0
  49. package/src/components/LogCard.tsx +491 -0
  50. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  51. package/src/components/MetaAccordion.tsx +340 -0
  52. package/src/components/MiniBarChart.tsx +42 -0
  53. package/src/components/MiniLineChart.tsx +33 -0
  54. package/src/components/NetworkIcons.tsx +2118 -0
  55. package/src/components/SectionHeader.tsx +113 -0
  56. package/src/components/SegmentedTabs.tsx +83 -0
  57. package/src/components/Slider.tsx +300 -0
  58. package/src/components/SourcePageCard.tsx +148 -0
  59. package/src/components/Toast.tsx +131 -0
  60. package/src/components/TouchableScale.tsx +91 -0
  61. package/src/components/TreeNode.tsx +186 -0
  62. package/src/console.ts +24 -0
  63. package/src/constants/index.ts +38 -0
  64. package/src/constants/version.ts +3 -0
  65. package/src/crash.ts +32 -0
  66. package/src/customHooks/analyticsLogger.ts +336 -0
  67. package/src/customHooks/bundleAnalyzer.ts +1231 -0
  68. package/src/customHooks/consoleLogger.ts +497 -0
  69. package/src/customHooks/crashHandler.ts +944 -0
  70. package/src/customHooks/logFilters.ts +32 -0
  71. package/src/customHooks/networkLogger.ts +419 -0
  72. package/src/customHooks/performanceTracker.ts +1014 -0
  73. package/src/customHooks/reduxLogger.ts +406 -0
  74. package/src/customHooks/useAccordion.tsx +60 -0
  75. package/src/decorators/index.ts +184 -0
  76. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  77. package/src/helpers/index.ts +860 -0
  78. package/src/helpers/memoryManager.ts +138 -0
  79. package/src/helpers/searchQueryParser.ts +283 -0
  80. package/src/helpers/settingsStore.ts +171 -0
  81. package/src/helpers/telemetry.ts +505 -0
  82. package/src/helpers/toast.ts +17 -0
  83. package/src/i18n/index.ts +69 -0
  84. package/src/i18n/locales/en.json +1052 -0
  85. package/src/index.tsx +2336 -0
  86. package/src/native/NativeInspector.ts +397 -0
  87. package/src/native/NativeNetworkInspector.ts +24 -0
  88. package/src/network.ts +22 -0
  89. package/src/performance.ts +38 -0
  90. package/src/redux.ts +23 -0
  91. package/src/styles/AppColors.ts +408 -0
  92. package/src/styles/AppFonts.ts +8 -0
  93. package/src/styles/common.ts +209 -0
  94. package/src/styles/index.ts +1570 -0
  95. package/src/types/enums.ts +194 -0
  96. package/src/types/index.ts +34 -0
  97. package/src/types/interfaces.ts +515 -0
@@ -0,0 +1,865 @@
1
+ import React, {useState, useMemo} from 'react';
2
+ import {
3
+ Pressable,
4
+ ScrollView,
5
+ StyleSheet,
6
+ Text,
7
+ TextInput,
8
+ View,
9
+ } from 'react-native';
10
+
11
+ // Components
12
+ import CopyButton from './CopyButton';
13
+ import JsonViewer from './JsonViewer';
14
+ import SegmentedTabs from './SegmentedTabs';
15
+ import TouchableScale from './TouchableScale';
16
+ import HighlightText from './HighlightText';
17
+ import AnimatedEntrance from './AnimatedEntrance';
18
+
19
+ // Helpers & Icons
20
+ import {AppFonts} from '../styles/AppFonts';
21
+ import {AppColors} from '../styles/AppColors';
22
+ import {getSize, formatDateTime, formatTime} from '../helpers';
23
+ import {getEventCategory} from '../helpers/gaAnalyticsRegistry';
24
+ import {useTranslation} from '../i18n';
25
+ import {
26
+ SparkleIcon,
27
+ ClockIcon,
28
+ SearchIcon,
29
+ ClearIcon,
30
+ LayersIcon,
31
+ CartIcon,
32
+ GlobeIcon,
33
+ BoltIcon,
34
+ PinIcon,
35
+ MoneyIcon,
36
+ PrettyIcon,
37
+ RawIcon,
38
+ TableIcon,
39
+ DocIcon,
40
+ } from './NetworkIcons';
41
+
42
+ // Type Definition
43
+ import {AnalyticsEvent} from '../types';
44
+
45
+ type DetailViewTab = 'overview' | 'tree' | 'raw';
46
+
47
+ const AnalyticsDetail = ({
48
+ event,
49
+ }: {
50
+ event: AnalyticsEvent;
51
+ }): React.JSX.Element => {
52
+ const {t} = useTranslation();
53
+ const [activeViewTab, setActiveViewTab] = useState<DetailViewTab>('overview');
54
+ const [paramSearch, setParamSearch] = useState('');
55
+
56
+ const params = useMemo(() => event.params ?? {}, [event.params]);
57
+ const userProperties = useMemo(() => event.userProperties ?? {}, [event.userProperties]);
58
+
59
+ const paramKeys = useMemo(() => Object.keys(params), [params]);
60
+ const userPropKeys = useMemo(() => Object.keys(userProperties), [userProperties]);
61
+
62
+ const category = useMemo(() => getEventCategory(event.name, params), [event.name, params]);
63
+
64
+ // Combined full JSON payload
65
+ const fullJsonData = useMemo(() => ({
66
+ name: event.name,
67
+ timestamp: event.timestamp,
68
+ source: event.source,
69
+ ...(event.screenName ? {screenName: event.screenName} : {}),
70
+ ...(event.screenClass ? {screenClass: event.screenClass} : {}),
71
+ ...(event.userId ? {userId: event.userId} : {}),
72
+ ...(event.sessionId ? {sessionId: event.sessionId} : {}),
73
+ ...(event.trackingId ? {trackingId: event.trackingId} : {}),
74
+ params,
75
+ ...(userPropKeys.length > 0 ? {userProperties} : {}),
76
+ }), [event, params, userPropKeys.length, userProperties]);
77
+
78
+ // E-commerce revenue calculation
79
+ const revenueInfo = useMemo(() => {
80
+ const val = params.value ?? params.price;
81
+ const currency = params.currency ?? 'USD';
82
+ if (val !== undefined && val !== null && !isNaN(Number(val))) {
83
+ return {
84
+ amount: Number(val).toFixed(2),
85
+ currency: String(currency).toUpperCase(),
86
+ };
87
+ }
88
+ return null;
89
+ }, [params]);
90
+
91
+ // Items count if an items array exists
92
+ const itemsCount = useMemo(() => {
93
+ if (Array.isArray(params.items)) {
94
+ return params.items.length;
95
+ }
96
+ return null;
97
+ }, [params.items]);
98
+
99
+ // Screen Context
100
+ const screenContext = useMemo(() => {
101
+ return (
102
+ event.screenName ||
103
+ params.firebase_screen ||
104
+ params.screen_name ||
105
+ params.page_title ||
106
+ event.screenClass ||
107
+ params.firebase_screen_class
108
+ );
109
+ }, [event.screenName, event.screenClass, params]);
110
+
111
+ // Filtered parameter entries
112
+ const filteredParamEntries = useMemo(() => {
113
+ const entries = Object.entries(params);
114
+ if (!paramSearch.trim()) return entries;
115
+ const q = paramSearch.toLowerCase();
116
+ return entries.filter(([k, v]) => {
117
+ const valStr = typeof v === 'object' ? JSON.stringify(v) : String(v);
118
+ return k.toLowerCase().includes(q) || valStr.toLowerCase().includes(q);
119
+ });
120
+ }, [params, paramSearch]);
121
+
122
+ // Category Theme Meta
123
+ const catMeta = useMemo(() => {
124
+ switch (category) {
125
+ case 'ecommerce':
126
+ return {
127
+ label: t('analytics.ecommerceCategory'),
128
+ color: AppColors.amber700,
129
+ bg: AppColors.amber100,
130
+ border: AppColors.amber200,
131
+ icon: (color: string) => <CartIcon color={color} size={11} />,
132
+ };
133
+ case 'page_view':
134
+ return {
135
+ label: t('analytics.screensCategory'),
136
+ color: AppColors.sky600,
137
+ bg: AppColors.sky100,
138
+ border: AppColors.sky400,
139
+ icon: (color: string) => <GlobeIcon color={color} size={11} />,
140
+ };
141
+ case 'system':
142
+ return {
143
+ label: t('analytics.systemCategory'),
144
+ color: AppColors.purple,
145
+ bg: AppColors.purple100,
146
+ border: AppColors.purple200,
147
+ icon: (color: string) => <BoltIcon color={color} size={11} />,
148
+ };
149
+ default:
150
+ return {
151
+ label: t('analytics.customCategory'),
152
+ color: AppColors.brandPurple,
153
+ bg: AppColors.purple100,
154
+ border: AppColors.purple200,
155
+ icon: (color: string) => <SparkleIcon color={color} size={11} />,
156
+ };
157
+ }
158
+ }, [category, t]);
159
+
160
+ const viewTabs = [
161
+ {
162
+ key: 'overview',
163
+ label: `${t('analytics.overviewTab')} (${paramKeys.length})`,
164
+ icon: (isActive: boolean) => (
165
+ <TableIcon
166
+ color={isActive ? AppColors.white : AppColors.grayText}
167
+ size={12}
168
+ />
169
+ ),
170
+ },
171
+ {
172
+ key: 'tree',
173
+ label: t('analytics.jsonTreeTab'),
174
+ icon: (isActive: boolean) => (
175
+ <PrettyIcon
176
+ color={isActive ? AppColors.white : AppColors.grayText}
177
+ size={12}
178
+ />
179
+ ),
180
+ },
181
+ {
182
+ key: 'raw',
183
+ label: t('analytics.rawPayloadTab'),
184
+ icon: (isActive: boolean) => (
185
+ <RawIcon
186
+ color={isActive ? AppColors.white : AppColors.grayText}
187
+ size={12}
188
+ />
189
+ ),
190
+ },
191
+ ];
192
+
193
+ return (
194
+ <ScrollView
195
+ style={detailStyles.scroll}
196
+ contentContainerStyle={detailStyles.content}
197
+ showsVerticalScrollIndicator
198
+ contentInsetAdjustmentBehavior="never"
199
+ automaticallyAdjustContentInsets={false}>
200
+
201
+ {/* ── 1. Hero Event Header Card ────────────────────────────────────── */}
202
+ <View style={detailStyles.heroCard}>
203
+ {/* Top Badges Row */}
204
+ <View style={detailStyles.heroTopRow}>
205
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap', flex: 1}}>
206
+ {/* Source Badge */}
207
+ <View
208
+ style={[
209
+ detailStyles.sourceBadge,
210
+ event.source === 'firebase'
211
+ ? {backgroundColor: AppColors.amberBg, borderColor: AppColors.amberBorder}
212
+ : {backgroundColor: AppColors.purpleBg, borderColor: AppColors.purpleBorder},
213
+ ]}>
214
+ <View
215
+ style={[
216
+ detailStyles.sourceDot,
217
+ {
218
+ backgroundColor:
219
+ event.source === 'firebase'
220
+ ? AppColors.firebaseOrange
221
+ : AppColors.purple,
222
+ },
223
+ ]}
224
+ />
225
+ <Text
226
+ style={[
227
+ detailStyles.sourceBadgeText,
228
+ {
229
+ color:
230
+ event.source === 'firebase'
231
+ ? AppColors.amber800Warm
232
+ : AppColors.purple,
233
+ },
234
+ ]}>
235
+ {event.source === 'firebase' ? t('analytics.sourceFirebase') : t('analytics.sourceCustom')}
236
+ </Text>
237
+ </View>
238
+
239
+ {/* Category Badge */}
240
+ <View
241
+ style={[
242
+ detailStyles.catBadge,
243
+ {backgroundColor: catMeta.bg, borderColor: catMeta.border},
244
+ ]}>
245
+ {catMeta.icon(catMeta.color)}
246
+ <Text style={[detailStyles.catBadgeText, {color: catMeta.color}]}>
247
+ {catMeta.label}
248
+ </Text>
249
+ </View>
250
+ </View>
251
+
252
+ {/* Copy Full Event JSON Button */}
253
+ <CopyButton
254
+ value={JSON.stringify(fullJsonData, null, 2)}
255
+ label={t('analytics.eventDetails')}
256
+ />
257
+ </View>
258
+
259
+ {/* Event Name Heading */}
260
+ <View style={detailStyles.titleRow}>
261
+ <Text style={detailStyles.eventTitle} selectable numberOfLines={2}>
262
+ {event.name}
263
+ </Text>
264
+ </View>
265
+
266
+ {/* Sub Details Row: Timestamp & Screen Location */}
267
+ <View style={detailStyles.metaRow}>
268
+ <View style={detailStyles.timePill}>
269
+ <ClockIcon color={AppColors.purple} size={11} />
270
+ <Text style={detailStyles.timeText}>
271
+ {formatDateTime(event.timestamp)}
272
+ </Text>
273
+ </View>
274
+
275
+ {screenContext && (
276
+ <View style={detailStyles.screenPill}>
277
+ <PinIcon color={AppColors.sky600} size={11} />
278
+ <Text style={detailStyles.screenText} numberOfLines={1}>
279
+ {String(screenContext)}
280
+ </Text>
281
+ </View>
282
+ )}
283
+ </View>
284
+
285
+ {/* ── 2. Metric Pills Summary ──────────────────────────────────────── */}
286
+ <View style={detailStyles.metricsBar}>
287
+ <View style={detailStyles.metricPill}>
288
+ <Text style={detailStyles.metricLabel}>Params:</Text>
289
+ <Text style={detailStyles.metricValue}>{paramKeys.length}</Text>
290
+ </View>
291
+
292
+ {userPropKeys.length > 0 && (
293
+ <View style={detailStyles.metricPill}>
294
+ <Text style={detailStyles.metricLabel}>User Props:</Text>
295
+ <Text style={detailStyles.metricValue}>{userPropKeys.length}</Text>
296
+ </View>
297
+ )}
298
+
299
+ <View style={detailStyles.metricPill}>
300
+ <Text style={detailStyles.metricLabel}>Size:</Text>
301
+ <Text style={detailStyles.metricValue}>{getSize(fullJsonData)}</Text>
302
+ </View>
303
+
304
+ {revenueInfo && (
305
+ <View style={detailStyles.revenuePill}>
306
+ <MoneyIcon color={AppColors.emerald600} size={11} />
307
+ <Text style={detailStyles.revenueValue}>
308
+ ${revenueInfo.amount} {revenueInfo.currency}
309
+ </Text>
310
+ </View>
311
+ )}
312
+
313
+ {itemsCount !== null && (
314
+ <View style={detailStyles.itemsPill}>
315
+ <Text style={detailStyles.itemsValue}>
316
+ 📦 {itemsCount} items
317
+ </Text>
318
+ </View>
319
+ )}
320
+ </View>
321
+ </View>
322
+
323
+ {/* ── 3. View Switcher Tabs (Overview, Tree, Raw) ───────────────────── */}
324
+ <View style={detailStyles.tabsWrap}>
325
+ <SegmentedTabs
326
+ tabs={viewTabs}
327
+ activeKey={activeViewTab}
328
+ onChange={key => setActiveViewTab(key as DetailViewTab)}
329
+ />
330
+ </View>
331
+
332
+ {/* ── 4. Main Tab Body Content ───────────────────────────────────────── */}
333
+ {activeViewTab === 'overview' && (
334
+ <View style={detailStyles.tabContent}>
335
+ {/* Metadata Card (Session, User ID, Client ID) */}
336
+ {(event.userId || event.sessionId || event.trackingId) && (
337
+ <View style={detailStyles.sectionCard}>
338
+ <View style={detailStyles.sectionHeader}>
339
+ <LayersIcon color={AppColors.purple} size={12} />
340
+ <Text style={detailStyles.sectionTitle}>SESSION & IDENTITY</Text>
341
+ </View>
342
+ <View style={detailStyles.sectionBody}>
343
+ {event.userId && (
344
+ <View style={detailStyles.kvRow}>
345
+ <Text style={detailStyles.kvKey}>User ID:</Text>
346
+ <Text style={detailStyles.kvVal} selectable>{event.userId}</Text>
347
+ </View>
348
+ )}
349
+ {event.sessionId && (
350
+ <View style={detailStyles.kvRow}>
351
+ <Text style={detailStyles.kvKey}>Session ID:</Text>
352
+ <Text style={detailStyles.kvVal} selectable>{event.sessionId}</Text>
353
+ </View>
354
+ )}
355
+ {event.trackingId && (
356
+ <View style={detailStyles.kvRow}>
357
+ <Text style={detailStyles.kvKey}>Tracking ID:</Text>
358
+ <Text style={detailStyles.kvVal} selectable>{event.trackingId}</Text>
359
+ </View>
360
+ )}
361
+ </View>
362
+ </View>
363
+ )}
364
+
365
+ {/* Parameters Table Section */}
366
+ <View style={detailStyles.sectionCard}>
367
+ <View style={detailStyles.sectionHeaderBetween}>
368
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
369
+ <TableIcon color={AppColors.brandPurple} size={12} />
370
+ <Text style={detailStyles.sectionTitle}>
371
+ EVENT PARAMETERS ({paramKeys.length})
372
+ </Text>
373
+ </View>
374
+ <Text style={detailStyles.sectionSubtitle}>
375
+ Key-Value Attributes
376
+ </Text>
377
+ </View>
378
+
379
+ {/* Search filter within parameters (if > 4 params) */}
380
+ {paramKeys.length > 4 && (
381
+ <View style={detailStyles.searchBar}>
382
+ <SearchIcon color={AppColors.grayTextWeak} size={13} />
383
+ <TextInput
384
+ placeholder="Filter parameters by key or value..."
385
+ placeholderTextColor={AppColors.grayTextWeak}
386
+ value={paramSearch}
387
+ onChangeText={setParamSearch}
388
+ style={detailStyles.searchInput}
389
+ autoCorrect={false}
390
+ autoCapitalize="none"
391
+ />
392
+ {paramSearch.length > 0 && (
393
+ <Pressable onPress={() => setParamSearch('')} hitSlop={8}>
394
+ <ClearIcon color={AppColors.grayTextWeak} size={13} />
395
+ </Pressable>
396
+ )}
397
+ </View>
398
+ )}
399
+
400
+ {paramKeys.length === 0 ? (
401
+ <View style={detailStyles.emptyParamsBox}>
402
+ <DocIcon color={AppColors.grayTextWeak} size={16} />
403
+ <Text style={detailStyles.emptyParamsText}>
404
+ No custom parameters recorded for this event.
405
+ </Text>
406
+ </View>
407
+ ) : filteredParamEntries.length === 0 ? (
408
+ <View style={detailStyles.emptyParamsBox}>
409
+ <Text style={detailStyles.emptyParamsText}>
410
+ No parameters matching "{paramSearch}"
411
+ </Text>
412
+ </View>
413
+ ) : (
414
+ <View style={detailStyles.paramList}>
415
+ {filteredParamEntries.map(([key, val], pIdx) => {
416
+ const isObject = typeof val === 'object' && val !== null;
417
+ const isArray = Array.isArray(val);
418
+ let typeStr: string = typeof val;
419
+ if (val === null) typeStr = 'null';
420
+ else if (isArray) typeStr = `array [${val.length}]`;
421
+ else if (isObject) typeStr = 'object';
422
+
423
+ const valString = isObject ? JSON.stringify(val, null, 2) : String(val);
424
+
425
+ // Highlight standard GA4 ecom/screen keys
426
+ const isKeyHighlighted =
427
+ key === 'value' ||
428
+ key === 'price' ||
429
+ key === 'currency' ||
430
+ key === 'screen_name' ||
431
+ key === 'item_id' ||
432
+ key === 'item_name';
433
+
434
+ return (
435
+ <AnimatedEntrance key={key} index={pIdx} distance={4}>
436
+ <View style={detailStyles.paramCard}>
437
+ {/* Param Top Row: Key + Type + Copy */}
438
+ <View style={detailStyles.paramCardHeader}>
439
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flex: 1, minWidth: 0}}>
440
+ <HighlightText
441
+ text={key}
442
+ search={paramSearch}
443
+ style={[
444
+ detailStyles.paramKeyText,
445
+ isKeyHighlighted && detailStyles.paramKeyHighlighted,
446
+ ]}
447
+ highlightStyle={detailStyles.highlight}
448
+ />
449
+ <View style={detailStyles.typeBadge}>
450
+ <Text style={detailStyles.typeBadgeText}>
451
+ {typeStr.toUpperCase()}
452
+ </Text>
453
+ </View>
454
+ </View>
455
+
456
+ <CopyButton
457
+ value={valString}
458
+ label={key}
459
+ />
460
+ </View>
461
+
462
+ {/* Param Value Display */}
463
+ <View style={detailStyles.paramValBox}>
464
+ {isObject ? (
465
+ <JsonViewer
466
+ data={val}
467
+ search={paramSearch}
468
+ hideTabs
469
+ wrap
470
+ />
471
+ ) : (
472
+ <HighlightText
473
+ text={valString}
474
+ search={paramSearch}
475
+ style={detailStyles.paramValText}
476
+ highlightStyle={detailStyles.highlight}
477
+ selectable
478
+ />
479
+ )}
480
+ </View>
481
+ </View>
482
+ </AnimatedEntrance>
483
+ );
484
+ })}
485
+ </View>
486
+ )}
487
+ </View>
488
+
489
+ {/* User Properties Section (if any) */}
490
+ {userPropKeys.length > 0 && (
491
+ <View style={detailStyles.sectionCard}>
492
+ <View style={detailStyles.sectionHeader}>
493
+ <SparkleIcon color={AppColors.purple} size={12} />
494
+ <Text style={detailStyles.sectionTitle}>
495
+ USER PROPERTIES SNAPSHOT ({userPropKeys.length})
496
+ </Text>
497
+ </View>
498
+ <View style={detailStyles.sectionBody}>
499
+ {userPropKeys.map(k => (
500
+ <View key={k} style={detailStyles.kvRow}>
501
+ <Text style={detailStyles.kvKey}>{k}:</Text>
502
+ <Text style={detailStyles.kvVal} selectable>
503
+ {String(userProperties[k])}
504
+ </Text>
505
+ </View>
506
+ ))}
507
+ </View>
508
+ </View>
509
+ )}
510
+ </View>
511
+ )}
512
+
513
+ {/* ── JSON Tree View ────────────────────────────────────────────────── */}
514
+ {activeViewTab === 'tree' && (
515
+ <View style={detailStyles.jsonContainer}>
516
+ <JsonViewer
517
+ data={fullJsonData}
518
+ defaultExpandDepth={1}
519
+ mode="pretty"
520
+ fullHeight
521
+ />
522
+ </View>
523
+ )}
524
+
525
+ {/* ── Raw Payload View ──────────────────────────────────────────────── */}
526
+ {activeViewTab === 'raw' && (
527
+ <View style={detailStyles.jsonContainer}>
528
+ <JsonViewer
529
+ data={fullJsonData}
530
+ mode="raw"
531
+ fullHeight
532
+ />
533
+ </View>
534
+ )}
535
+ </ScrollView>
536
+ );
537
+ };
538
+
539
+ const detailStyles = StyleSheet.create({
540
+ scroll: {
541
+ flex: 1,
542
+ backgroundColor: AppColors.grayBackground,
543
+ },
544
+ content: {
545
+ paddingTop: 12,
546
+ paddingBottom: 40,
547
+ paddingHorizontal: 12,
548
+ },
549
+ heroCard: {
550
+ backgroundColor: AppColors.white,
551
+ borderRadius: 12,
552
+ padding: 14,
553
+ borderWidth: 1,
554
+ borderColor: AppColors.dividerColor,
555
+ shadowColor: AppColors.primaryBlack,
556
+ shadowOffset: {width: 0, height: 1},
557
+ shadowOpacity: 0.04,
558
+ shadowRadius: 4,
559
+ elevation: 2,
560
+ marginBottom: 12,
561
+ },
562
+ heroTopRow: {
563
+ flexDirection: 'row',
564
+ alignItems: 'center',
565
+ justifyContent: 'space-between',
566
+ marginBottom: 10,
567
+ },
568
+ sourceBadge: {
569
+ flexDirection: 'row',
570
+ alignItems: 'center',
571
+ gap: 4.5,
572
+ paddingHorizontal: 7,
573
+ paddingVertical: 3,
574
+ borderRadius: 5,
575
+ borderWidth: 1,
576
+ },
577
+ sourceDot: {
578
+ width: 6,
579
+ height: 6,
580
+ borderRadius: 3,
581
+ },
582
+ sourceBadgeText: {
583
+ fontFamily: AppFonts.interBold,
584
+ fontSize: 9.5,
585
+ letterSpacing: 0.4,
586
+ },
587
+ catBadge: {
588
+ flexDirection: 'row',
589
+ alignItems: 'center',
590
+ gap: 4,
591
+ paddingHorizontal: 7,
592
+ paddingVertical: 3,
593
+ borderRadius: 5,
594
+ borderWidth: 1,
595
+ },
596
+ catBadgeText: {
597
+ fontFamily: AppFonts.interBold,
598
+ fontSize: 9.5,
599
+ letterSpacing: 0.3,
600
+ },
601
+ titleRow: {
602
+ marginBottom: 8,
603
+ },
604
+ eventTitle: {
605
+ fontFamily: AppFonts.interBold,
606
+ fontSize: 16,
607
+ color: AppColors.primaryBlack,
608
+ lineHeight: 22,
609
+ },
610
+ metaRow: {
611
+ flexDirection: 'row',
612
+ alignItems: 'center',
613
+ gap: 6,
614
+ flexWrap: 'wrap',
615
+ marginBottom: 10,
616
+ },
617
+ timePill: {
618
+ flexDirection: 'row',
619
+ alignItems: 'center',
620
+ gap: 4,
621
+ backgroundColor: `${AppColors.purple}14`,
622
+ paddingHorizontal: 7,
623
+ paddingVertical: 3,
624
+ borderRadius: 5,
625
+ borderWidth: 1,
626
+ borderColor: `${AppColors.purple}2E`,
627
+ },
628
+ timeText: {
629
+ fontFamily: AppFonts.interMedium,
630
+ fontSize: 10.5,
631
+ color: AppColors.purple,
632
+ },
633
+ screenPill: {
634
+ flexDirection: 'row',
635
+ alignItems: 'center',
636
+ gap: 4,
637
+ backgroundColor: `${AppColors.sky500}14`,
638
+ paddingHorizontal: 7,
639
+ paddingVertical: 3,
640
+ borderRadius: 5,
641
+ borderWidth: 1,
642
+ borderColor: `${AppColors.sky500}2E`,
643
+ maxWidth: 220,
644
+ },
645
+ screenText: {
646
+ fontFamily: AppFonts.interMedium,
647
+ fontSize: 10.5,
648
+ color: AppColors.sky600,
649
+ },
650
+ metricsBar: {
651
+ flexDirection: 'row',
652
+ alignItems: 'center',
653
+ gap: 6,
654
+ flexWrap: 'wrap',
655
+ paddingTop: 8,
656
+ borderTopWidth: 1,
657
+ borderTopColor: AppColors.dividerColor,
658
+ },
659
+ metricPill: {
660
+ flexDirection: 'row',
661
+ alignItems: 'center',
662
+ gap: 4,
663
+ backgroundColor: AppColors.grayBackground,
664
+ paddingHorizontal: 7,
665
+ paddingVertical: 3,
666
+ borderRadius: 5,
667
+ borderWidth: 1,
668
+ borderColor: AppColors.dividerColor,
669
+ },
670
+ metricLabel: {
671
+ fontFamily: AppFonts.interMedium,
672
+ fontSize: 10.5,
673
+ color: AppColors.grayTextWeak,
674
+ },
675
+ metricValue: {
676
+ fontFamily: AppFonts.interBold,
677
+ fontSize: 10.5,
678
+ color: AppColors.primaryBlack,
679
+ },
680
+ revenuePill: {
681
+ flexDirection: 'row',
682
+ alignItems: 'center',
683
+ gap: 4,
684
+ backgroundColor: AppColors.emeraldBg,
685
+ paddingHorizontal: 7,
686
+ paddingVertical: 3,
687
+ borderRadius: 5,
688
+ borderWidth: 1,
689
+ borderColor: AppColors.emeraldBorder,
690
+ },
691
+ revenueValue: {
692
+ fontFamily: AppFonts.interBold,
693
+ fontSize: 10.5,
694
+ color: '#047857',
695
+ },
696
+ itemsPill: {
697
+ backgroundColor: '#EFF6FF',
698
+ paddingHorizontal: 7,
699
+ paddingVertical: 3,
700
+ borderRadius: 5,
701
+ borderWidth: 1,
702
+ borderColor: '#BFDBFE',
703
+ },
704
+ itemsValue: {
705
+ fontFamily: AppFonts.interBold,
706
+ fontSize: 10.5,
707
+ color: '#1D4ED8',
708
+ },
709
+ tabsWrap: {
710
+ marginBottom: 12,
711
+ },
712
+ tabContent: {
713
+ gap: 12,
714
+ },
715
+ sectionCard: {
716
+ backgroundColor: AppColors.white,
717
+ borderRadius: 12,
718
+ padding: 12,
719
+ borderWidth: 1,
720
+ borderColor: AppColors.dividerColor,
721
+ shadowColor: AppColors.primaryBlack,
722
+ shadowOffset: {width: 0, height: 1},
723
+ shadowOpacity: 0.04,
724
+ shadowRadius: 4,
725
+ elevation: 2,
726
+ },
727
+ sectionHeader: {
728
+ flexDirection: 'row',
729
+ alignItems: 'center',
730
+ gap: 6,
731
+ marginBottom: 10,
732
+ paddingBottom: 6,
733
+ borderBottomWidth: 1,
734
+ borderBottomColor: AppColors.dividerColor,
735
+ },
736
+ sectionHeaderBetween: {
737
+ flexDirection: 'row',
738
+ alignItems: 'center',
739
+ justifyContent: 'space-between',
740
+ marginBottom: 10,
741
+ paddingBottom: 6,
742
+ borderBottomWidth: 1,
743
+ borderBottomColor: AppColors.dividerColor,
744
+ },
745
+ sectionTitle: {
746
+ fontFamily: AppFonts.interBold,
747
+ fontSize: 11,
748
+ color: AppColors.slate700,
749
+ letterSpacing: 0.4,
750
+ },
751
+ sectionSubtitle: {
752
+ fontFamily: AppFonts.interMedium,
753
+ fontSize: 10,
754
+ color: AppColors.grayTextWeak,
755
+ },
756
+ sectionBody: {
757
+ gap: 6,
758
+ },
759
+ kvRow: {
760
+ flexDirection: 'row',
761
+ alignItems: 'center',
762
+ justifyContent: 'space-between',
763
+ paddingVertical: 3,
764
+ },
765
+ kvKey: {
766
+ fontFamily: AppFonts.interMedium,
767
+ fontSize: 11,
768
+ color: AppColors.grayTextWeak,
769
+ },
770
+ kvVal: {
771
+ fontFamily: AppFonts.interBold,
772
+ fontSize: 11,
773
+ color: AppColors.primaryBlack,
774
+ },
775
+ searchBar: {
776
+ flexDirection: 'row',
777
+ alignItems: 'center',
778
+ backgroundColor: AppColors.grayBackground,
779
+ borderRadius: 8,
780
+ paddingHorizontal: 8,
781
+ paddingVertical: 5,
782
+ borderWidth: 1,
783
+ borderColor: AppColors.dividerColor,
784
+ marginBottom: 10,
785
+ gap: 6,
786
+ },
787
+ searchInput: {
788
+ flex: 1,
789
+ fontFamily: AppFonts.interRegular,
790
+ fontSize: 11,
791
+ color: AppColors.primaryBlack,
792
+ paddingVertical: 0,
793
+ },
794
+ emptyParamsBox: {
795
+ alignItems: 'center',
796
+ justifyContent: 'center',
797
+ paddingVertical: 20,
798
+ gap: 6,
799
+ },
800
+ emptyParamsText: {
801
+ fontFamily: AppFonts.interMedium,
802
+ fontSize: 11,
803
+ color: AppColors.grayTextWeak,
804
+ },
805
+ paramList: {
806
+ gap: 8,
807
+ },
808
+ paramCard: {
809
+ backgroundColor: AppColors.grayBackground,
810
+ borderRadius: 8,
811
+ padding: 9,
812
+ borderWidth: 1,
813
+ borderColor: AppColors.dividerColor,
814
+ },
815
+ paramCardHeader: {
816
+ flexDirection: 'row',
817
+ alignItems: 'center',
818
+ justifyContent: 'space-between',
819
+ marginBottom: 4,
820
+ },
821
+ paramKeyText: {
822
+ fontFamily: AppFonts.interBold,
823
+ fontSize: 11.5,
824
+ color: AppColors.primaryBlack,
825
+ },
826
+ paramKeyHighlighted: {
827
+ color: AppColors.brandPurple,
828
+ },
829
+ typeBadge: {
830
+ backgroundColor: '#E2E8F0',
831
+ paddingHorizontal: 5,
832
+ paddingVertical: 1.5,
833
+ borderRadius: 3,
834
+ },
835
+ typeBadgeText: {
836
+ fontFamily: AppFonts.interBold,
837
+ fontSize: 8.5,
838
+ color: AppColors.slate700,
839
+ letterSpacing: 0.3,
840
+ },
841
+ paramValBox: {
842
+ marginTop: 2,
843
+ },
844
+ paramValText: {
845
+ fontFamily: AppFonts.interRegular,
846
+ fontSize: 11,
847
+ color: AppColors.slate700,
848
+ lineHeight: 16,
849
+ },
850
+ jsonContainer: {
851
+ borderRadius: 12,
852
+ borderWidth: 1,
853
+ borderColor: AppColors.dividerColor,
854
+ overflow: 'hidden',
855
+ backgroundColor: AppColors.white,
856
+ minHeight: 300,
857
+ },
858
+ highlight: {
859
+ backgroundColor: '#FEF08A',
860
+ color: '#854D0E',
861
+ fontFamily: AppFonts.interBold,
862
+ },
863
+ });
864
+
865
+ export default AnalyticsDetail;