react-native-inapp-inspector 2.2.2 → 2.2.4

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