react-native-inapp-inspector 2.2.1 → 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 (181) hide show
  1. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  2. package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
  3. package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
  4. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  5. package/android/.gradle/8.9/executionHistory/executionHistory.lock +0 -0
  6. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  7. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  8. package/android/.gradle/8.9/gc.properties +0 -0
  9. package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
  10. package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
  11. package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
  12. package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
  13. package/android/.gradle/9.2.0/gc.properties +0 -0
  14. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  15. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/android/build/reports/problems/problems-report.html +659 -0
  18. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +28 -6
  19. package/dist/commonjs/analytics.d.ts +7 -0
  20. package/dist/commonjs/analytics.js +28 -0
  21. package/dist/commonjs/bundle.d.ts +6 -0
  22. package/dist/commonjs/bundle.js +20 -0
  23. package/dist/commonjs/console.d.ts +7 -0
  24. package/dist/commonjs/console.js +20 -0
  25. package/dist/commonjs/constants/version.d.ts +1 -1
  26. package/dist/commonjs/constants/version.js +1 -1
  27. package/dist/commonjs/crash.d.ts +7 -0
  28. package/dist/commonjs/crash.js +27 -0
  29. package/dist/commonjs/customHooks/analyticsLogger.d.ts +3 -0
  30. package/dist/commonjs/customHooks/analyticsLogger.js +23 -2
  31. package/dist/commonjs/customHooks/consoleLogger.d.ts +3 -0
  32. package/dist/commonjs/customHooks/consoleLogger.js +24 -4
  33. package/dist/commonjs/customHooks/crashHandler.d.ts +2 -0
  34. package/dist/commonjs/customHooks/crashHandler.js +15 -1
  35. package/dist/commonjs/customHooks/networkLogger.d.ts +3 -0
  36. package/dist/commonjs/customHooks/networkLogger.js +23 -2
  37. package/dist/commonjs/customHooks/performanceTracker.d.ts +3 -0
  38. package/dist/commonjs/customHooks/performanceTracker.js +34 -3
  39. package/dist/commonjs/customHooks/reduxLogger.d.ts +3 -0
  40. package/dist/commonjs/customHooks/reduxLogger.js +24 -4
  41. package/dist/commonjs/helpers/index.d.ts +1 -0
  42. package/dist/commonjs/helpers/index.js +1 -0
  43. package/dist/commonjs/helpers/memoryManager.d.ts +29 -0
  44. package/dist/commonjs/helpers/memoryManager.js +112 -0
  45. package/dist/commonjs/helpers/telemetry.js +4 -4
  46. package/dist/commonjs/index.d.ts +7 -1
  47. package/dist/commonjs/index.js +36 -2
  48. package/dist/commonjs/network.d.ts +6 -0
  49. package/dist/commonjs/network.js +17 -0
  50. package/dist/commonjs/performance.d.ts +6 -0
  51. package/dist/commonjs/performance.js +33 -0
  52. package/dist/commonjs/redux.d.ts +5 -0
  53. package/dist/commonjs/redux.js +24 -0
  54. package/dist/esm/analytics.d.ts +7 -0
  55. package/dist/esm/analytics.js +6 -0
  56. package/dist/esm/bundle.d.ts +6 -0
  57. package/dist/esm/bundle.js +6 -0
  58. package/dist/esm/console.d.ts +7 -0
  59. package/dist/esm/console.js +6 -0
  60. package/dist/esm/constants/version.d.ts +1 -1
  61. package/dist/esm/constants/version.js +1 -1
  62. package/dist/esm/crash.d.ts +7 -0
  63. package/dist/esm/crash.js +6 -0
  64. package/dist/esm/customHooks/analyticsLogger.d.ts +3 -0
  65. package/dist/esm/customHooks/analyticsLogger.js +19 -1
  66. package/dist/esm/customHooks/consoleLogger.d.ts +3 -0
  67. package/dist/esm/customHooks/consoleLogger.js +20 -3
  68. package/dist/esm/customHooks/crashHandler.d.ts +2 -0
  69. package/dist/esm/customHooks/crashHandler.js +12 -0
  70. package/dist/esm/customHooks/networkLogger.d.ts +3 -0
  71. package/dist/esm/customHooks/networkLogger.js +19 -1
  72. package/dist/esm/customHooks/performanceTracker.d.ts +3 -0
  73. package/dist/esm/customHooks/performanceTracker.js +30 -2
  74. package/dist/esm/customHooks/reduxLogger.d.ts +3 -0
  75. package/dist/esm/customHooks/reduxLogger.js +20 -3
  76. package/dist/esm/helpers/index.d.ts +1 -0
  77. package/dist/esm/helpers/index.js +1 -0
  78. package/dist/esm/helpers/memoryManager.d.ts +29 -0
  79. package/dist/esm/helpers/memoryManager.js +104 -0
  80. package/dist/esm/helpers/telemetry.js +4 -4
  81. package/dist/esm/index.d.ts +7 -1
  82. package/dist/esm/index.js +17 -3
  83. package/dist/esm/network.d.ts +6 -0
  84. package/dist/esm/network.js +5 -0
  85. package/dist/esm/performance.d.ts +6 -0
  86. package/dist/esm/performance.js +6 -0
  87. package/dist/esm/redux.d.ts +5 -0
  88. package/dist/esm/redux.js +5 -0
  89. package/package.json +53 -2
  90. package/src/analytics.ts +35 -0
  91. package/src/bundle.ts +25 -0
  92. package/src/components/AnalyticsDetail.tsx +865 -0
  93. package/src/components/AnalyticsEventCard.tsx +441 -0
  94. package/src/components/AnalyticsGraph.tsx +583 -0
  95. package/src/components/AnimatedEntrance.tsx +64 -0
  96. package/src/components/AppHeaderLogo.tsx +109 -0
  97. package/src/components/BrandCircleIcon.tsx +144 -0
  98. package/src/components/BrandSquareIcon.tsx +144 -0
  99. package/src/components/CodeSnippet.tsx +725 -0
  100. package/src/components/ConsoleLogCard.tsx +628 -0
  101. package/src/components/CopyButton.tsx +87 -0
  102. package/src/components/DiffViewer.tsx +82 -0
  103. package/src/components/DomainHeader.tsx +237 -0
  104. package/src/components/EmptyState.tsx +73 -0
  105. package/src/components/EndOfListFooter.tsx +100 -0
  106. package/src/components/ErrorBoundary.tsx +688 -0
  107. package/src/components/HeadersSection.tsx +192 -0
  108. package/src/components/HighlightText.tsx +100 -0
  109. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  110. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  111. package/src/components/Inspector/BundleTab.tsx +4731 -0
  112. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  113. package/src/components/Inspector/CrashDetail.tsx +941 -0
  114. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  115. package/src/components/Inspector/CrashTab.tsx +871 -0
  116. package/src/components/Inspector/FabLauncher.tsx +80 -0
  117. package/src/components/Inspector/InspectorContext.tsx +28 -0
  118. package/src/components/Inspector/InspectorHeader.tsx +973 -0
  119. package/src/components/Inspector/LogDetail.tsx +1427 -0
  120. package/src/components/Inspector/MainScreen.tsx +258 -0
  121. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  122. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  123. package/src/components/Inspector/NetworkTab.tsx +1095 -0
  124. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  125. package/src/components/Inspector/PerformanceTab.tsx +1894 -0
  126. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  127. package/src/components/Inspector/ReduxTab.tsx +954 -0
  128. package/src/components/Inspector/SettingsPanel.tsx +3181 -0
  129. package/src/components/Inspector/TabBar.tsx +187 -0
  130. package/src/components/Inspector/TelemetryConsentModal.tsx +392 -0
  131. package/src/components/Inspector/UpdateAvailableModal.tsx +545 -0
  132. package/src/components/JsonViewer.tsx +486 -0
  133. package/src/components/LogCard.tsx +491 -0
  134. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  135. package/src/components/MetaAccordion.tsx +340 -0
  136. package/src/components/MiniBarChart.tsx +42 -0
  137. package/src/components/MiniLineChart.tsx +33 -0
  138. package/src/components/NetworkIcons.tsx +2118 -0
  139. package/src/components/SectionHeader.tsx +113 -0
  140. package/src/components/SegmentedTabs.tsx +83 -0
  141. package/src/components/Slider.tsx +300 -0
  142. package/src/components/SourcePageCard.tsx +148 -0
  143. package/src/components/Toast.tsx +131 -0
  144. package/src/components/TouchableScale.tsx +91 -0
  145. package/src/components/TreeNode.tsx +186 -0
  146. package/src/console.ts +24 -0
  147. package/src/constants/index.ts +38 -0
  148. package/src/constants/version.ts +3 -0
  149. package/src/crash.ts +32 -0
  150. package/src/customHooks/analyticsLogger.ts +336 -0
  151. package/src/customHooks/bundleAnalyzer.ts +1231 -0
  152. package/src/customHooks/consoleLogger.ts +497 -0
  153. package/src/customHooks/crashHandler.ts +944 -0
  154. package/src/customHooks/logFilters.ts +32 -0
  155. package/src/customHooks/networkLogger.ts +419 -0
  156. package/src/customHooks/performanceTracker.ts +1014 -0
  157. package/src/customHooks/reduxLogger.ts +406 -0
  158. package/src/customHooks/useAccordion.tsx +60 -0
  159. package/src/decorators/index.ts +184 -0
  160. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  161. package/src/helpers/index.ts +860 -0
  162. package/src/helpers/memoryManager.ts +138 -0
  163. package/src/helpers/searchQueryParser.ts +283 -0
  164. package/src/helpers/settingsStore.ts +171 -0
  165. package/src/helpers/telemetry.ts +505 -0
  166. package/src/helpers/toast.ts +17 -0
  167. package/src/i18n/index.ts +69 -0
  168. package/src/i18n/locales/en.json +1052 -0
  169. package/src/index.tsx +2336 -0
  170. package/src/native/NativeInspector.ts +397 -0
  171. package/src/native/NativeNetworkInspector.ts +24 -0
  172. package/src/network.ts +22 -0
  173. package/src/performance.ts +38 -0
  174. package/src/redux.ts +23 -0
  175. package/src/styles/AppColors.ts +408 -0
  176. package/src/styles/AppFonts.ts +8 -0
  177. package/src/styles/common.ts +209 -0
  178. package/src/styles/index.ts +1570 -0
  179. package/src/types/enums.ts +194 -0
  180. package/src/types/index.ts +34 -0
  181. 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;