react-native-inapp-inspector 2.3.15 → 2.3.16

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 (96) hide show
  1. package/README.md +1 -0
  2. package/dist/commonjs/components/Inspector/AnalyticsTab.js +0 -22
  3. package/dist/commonjs/components/Inspector/ConsoleTab.js +0 -22
  4. package/dist/commonjs/components/Inspector/CrashTab.js +0 -23
  5. package/dist/commonjs/components/Inspector/FeedbackModal.d.ts +7 -0
  6. package/dist/commonjs/components/Inspector/FeedbackModal.js +552 -0
  7. package/dist/commonjs/components/Inspector/InspectorHeader.js +56 -4
  8. package/dist/commonjs/components/Inspector/MainScreen.js +45 -3
  9. package/dist/commonjs/components/Inspector/NetworkTab.js +27 -38
  10. package/dist/commonjs/components/Inspector/NpmStarPrompt.d.ts +3 -0
  11. package/dist/commonjs/components/Inspector/NpmStarPrompt.js +494 -0
  12. package/dist/commonjs/components/Inspector/PerformanceTab.js +8 -1
  13. package/dist/commonjs/components/Inspector/ReduxTab.js +0 -22
  14. package/dist/commonjs/components/Inspector/SettingsPanel.js +1035 -173
  15. package/dist/commonjs/components/Inspector/StorageTab.js +0 -23
  16. package/dist/commonjs/components/Inspector/TabBar.js +50 -15
  17. package/dist/commonjs/components/NetworkIcons.d.ts +5 -0
  18. package/dist/commonjs/components/NetworkIcons.js +33 -2
  19. package/dist/commonjs/constants/version.d.ts +1 -1
  20. package/dist/commonjs/constants/version.js +1 -1
  21. package/dist/commonjs/helpers/index.d.ts +1 -0
  22. package/dist/commonjs/helpers/index.js +1 -0
  23. package/dist/commonjs/helpers/remoteConfig.d.ts +4 -0
  24. package/dist/commonjs/helpers/remoteConfig.js +165 -0
  25. package/dist/commonjs/helpers/searchQueryParser.js +2 -0
  26. package/dist/commonjs/helpers/telemetry.d.ts +5 -0
  27. package/dist/commonjs/helpers/telemetry.js +339 -8
  28. package/dist/commonjs/index.d.ts +1 -0
  29. package/dist/commonjs/index.js +32 -2
  30. package/dist/commonjs/styles/AppFonts.d.ts +2 -0
  31. package/dist/commonjs/styles/AppFonts.js +1 -0
  32. package/dist/commonjs/styles/index.d.ts +22 -0
  33. package/dist/commonjs/styles/index.js +19 -0
  34. package/dist/commonjs/types/enums.d.ts +1 -0
  35. package/dist/commonjs/types/enums.js +1 -0
  36. package/dist/commonjs/types/interfaces.d.ts +12 -0
  37. package/dist/esm/components/Inspector/AnalyticsTab.js +1 -23
  38. package/dist/esm/components/Inspector/ConsoleTab.js +1 -23
  39. package/dist/esm/components/Inspector/CrashTab.js +1 -24
  40. package/dist/esm/components/Inspector/FeedbackModal.d.ts +7 -0
  41. package/dist/esm/components/Inspector/FeedbackModal.js +515 -0
  42. package/dist/esm/components/Inspector/InspectorHeader.js +57 -5
  43. package/dist/esm/components/Inspector/MainScreen.js +46 -4
  44. package/dist/esm/components/Inspector/NetworkTab.js +28 -39
  45. package/dist/esm/components/Inspector/NpmStarPrompt.d.ts +3 -0
  46. package/dist/esm/components/Inspector/NpmStarPrompt.js +454 -0
  47. package/dist/esm/components/Inspector/PerformanceTab.js +8 -1
  48. package/dist/esm/components/Inspector/ReduxTab.js +1 -23
  49. package/dist/esm/components/Inspector/SettingsPanel.js +1038 -176
  50. package/dist/esm/components/Inspector/StorageTab.js +0 -23
  51. package/dist/esm/components/Inspector/TabBar.js +50 -15
  52. package/dist/esm/components/NetworkIcons.d.ts +5 -0
  53. package/dist/esm/components/NetworkIcons.js +26 -0
  54. package/dist/esm/constants/version.d.ts +1 -1
  55. package/dist/esm/constants/version.js +1 -1
  56. package/dist/esm/helpers/index.d.ts +1 -0
  57. package/dist/esm/helpers/index.js +1 -0
  58. package/dist/esm/helpers/remoteConfig.d.ts +4 -0
  59. package/dist/esm/helpers/remoteConfig.js +160 -0
  60. package/dist/esm/helpers/searchQueryParser.js +2 -0
  61. package/dist/esm/helpers/telemetry.d.ts +5 -0
  62. package/dist/esm/helpers/telemetry.js +332 -9
  63. package/dist/esm/index.d.ts +1 -0
  64. package/dist/esm/index.js +30 -2
  65. package/dist/esm/styles/AppFonts.d.ts +2 -0
  66. package/dist/esm/styles/AppFonts.js +1 -0
  67. package/dist/esm/styles/index.d.ts +22 -0
  68. package/dist/esm/styles/index.js +19 -0
  69. package/dist/esm/types/enums.d.ts +1 -0
  70. package/dist/esm/types/enums.js +1 -0
  71. package/dist/esm/types/interfaces.d.ts +12 -0
  72. package/package.json +1 -1
  73. package/src/components/Inspector/AnalyticsTab.tsx +0 -23
  74. package/src/components/Inspector/ConsoleTab.tsx +0 -23
  75. package/src/components/Inspector/CrashTab.tsx +0 -23
  76. package/src/components/Inspector/FeedbackModal.tsx +626 -0
  77. package/src/components/Inspector/InspectorHeader.tsx +78 -2
  78. package/src/components/Inspector/MainScreen.tsx +59 -3
  79. package/src/components/Inspector/NetworkTab.tsx +69 -78
  80. package/src/components/Inspector/NpmStarPrompt.tsx +532 -0
  81. package/src/components/Inspector/PerformanceTab.tsx +8 -1
  82. package/src/components/Inspector/ReduxTab.tsx +0 -23
  83. package/src/components/Inspector/SettingsPanel.tsx +1162 -197
  84. package/src/components/Inspector/StorageTab.tsx +0 -23
  85. package/src/components/Inspector/TabBar.tsx +57 -20
  86. package/src/components/NetworkIcons.tsx +92 -0
  87. package/src/constants/version.ts +1 -1
  88. package/src/helpers/index.ts +1 -0
  89. package/src/helpers/remoteConfig.ts +212 -0
  90. package/src/helpers/searchQueryParser.ts +3 -0
  91. package/src/helpers/telemetry.ts +381 -10
  92. package/src/index.tsx +42 -0
  93. package/src/styles/AppFonts.ts +2 -0
  94. package/src/styles/index.ts +20 -1
  95. package/src/types/enums.ts +1 -0
  96. package/src/types/interfaces.ts +12 -0
@@ -13,7 +13,7 @@ import Svg, { Path, Rect, Line, Circle, G, Defs, LinearGradient as SvgLinearGrad
13
13
  import TouchableScale from '../TouchableScale';
14
14
  import { getCurrentUserId, getCurrentUserProperties, getDefaultEventParameters, getCollectionEnabled, } from '../../customHooks/analyticsLogger';
15
15
  import { getEventCategory, } from '../../helpers';
16
- import { LayersIcon, SearchIcon, ClearIcon, TrashIcon, HeaderPauseIcon, FilterIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, PinIcon, MoneyIcon, ChevronIcon, } from '../NetworkIcons';
16
+ import { LayersIcon, SearchIcon, ClearIcon, TrashIcon, HeaderPauseIcon, FilterIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, PinIcon, MoneyIcon, } from '../NetworkIcons';
17
17
  const AnalyticsHeader = React.memo(() => {
18
18
  const { t } = useTranslation();
19
19
  const { analyticsEvents, filteredAnalyticsEvents, analyticsFilters, setAnalyticsFilters, analyticsHeaderExpanded, setAnalyticsHeaderExpanded, } = useInspector();
@@ -934,28 +934,6 @@ const AnalyticsTab = React.memo(() => {
934
934
  flexGrow: 1,
935
935
  },
936
936
  ]} keyboardShouldPersistTaps="handled"/>
937
-
938
- <TouchableScale onPress={() => {
939
- try {
940
- listRef.current?.scrollToOffset({
941
- offset: 0,
942
- animated: true,
943
- });
944
- }
945
- catch {
946
- try {
947
- listRef.current?.scrollToIndex({
948
- index: 0,
949
- animated: true,
950
- });
951
- }
952
- catch { }
953
- }
954
- }} hitSlop={12} style={styles.scrollTopBtn}>
955
- <View style={{ transform: [{ rotate: '180deg' }] }}>
956
- <ChevronIcon color={AppColors.white} size={18}/>
957
- </View>
958
- </TouchableScale>
959
937
  </>) : null}
960
938
  </View>
961
939
  </>);
@@ -10,7 +10,7 @@ import EndOfListFooter from '../EndOfListFooter';
10
10
  import styles from '../../styles';
11
11
  import { AppColors } from '../../styles/AppColors';
12
12
  import { AppFonts } from '../../styles/AppFonts';
13
- import { SearchIcon, ClearIcon, SortArrowIcon, TrashIcon, LayersIcon, UserIcon, InfoCircleIcon, WarningTriangleIcon, ErrorCircleIcon, AnalyticsIcon, HeaderPauseIcon, ChevronIcon, } from '../NetworkIcons';
13
+ import { SearchIcon, ClearIcon, SortArrowIcon, TrashIcon, LayersIcon, UserIcon, InfoCircleIcon, WarningTriangleIcon, ErrorCircleIcon, AnalyticsIcon, HeaderPauseIcon, } from '../NetworkIcons';
14
14
  const ConsoleTab = React.memo(() => {
15
15
  const { t } = useTranslation();
16
16
  const { logSearch, setLogSearch, logSortOrder, setLogSortOrder, handleDelete, logFilters, setLogFilters, logCounts, filteredConsoleLogs, visibleConsoleLogs, isConsolePaused, setIsConsolePaused, } = useInspector();
@@ -499,28 +499,6 @@ const ConsoleTab = React.memo(() => {
499
499
  styles.listContent,
500
500
  filteredConsoleLogs.length === 0 && { flexGrow: 1 },
501
501
  ]} keyboardShouldPersistTaps="handled"/>
502
-
503
- <TouchableScale onPress={() => {
504
- try {
505
- listRef.current?.scrollToOffset({
506
- offset: 0,
507
- animated: true,
508
- });
509
- }
510
- catch {
511
- try {
512
- listRef.current?.scrollToIndex({
513
- index: 0,
514
- animated: true,
515
- });
516
- }
517
- catch { }
518
- }
519
- }} hitSlop={12} style={styles.scrollTopBtn}>
520
- <View style={{ transform: [{ rotate: '180deg' }] }}>
521
- <ChevronIcon color={AppColors.white} size={18}/>
522
- </View>
523
- </TouchableScale>
524
502
  </View>);
525
503
  });
526
504
  export default ConsoleTab;
@@ -7,12 +7,11 @@ import AnimatedEntrance from '../AnimatedEntrance';
7
7
  import EndOfListFooter from '../EndOfListFooter';
8
8
  import FeatureUnderDevNotice from './FeatureUnderDevNotice';
9
9
  import HighlightText from '../HighlightText';
10
- import styles from '../../styles';
11
10
  import { AppColors } from '../../styles/AppColors';
12
11
  import { AppFonts } from '../../styles/AppFonts';
13
12
  import { clearCrashRecords, } from '../../customHooks/crashHandler';
14
13
  import CrashFilterModal, { DEFAULT_CRASH_FILTERS, isCrashFiltersDefault, } from './CrashFilterModal';
15
- import { SearchIcon, ClearIcon, TrashIcon, TerminalIcon, LayersIcon, ClockIcon, ShieldAlertIcon, FlameIcon, JsIcon, HourglassIcon, SkullIcon, LayoutIcon, ChipIcon, AppleIcon, AndroidIcon, RepeatIcon, CodeBracketsIcon, FilterIcon, ChevronIcon, } from '../NetworkIcons';
14
+ import { SearchIcon, ClearIcon, TrashIcon, TerminalIcon, LayersIcon, ClockIcon, ShieldAlertIcon, FlameIcon, JsIcon, HourglassIcon, SkullIcon, LayoutIcon, ChipIcon, AppleIcon, AndroidIcon, RepeatIcon, CodeBracketsIcon, FilterIcon, } from '../NetworkIcons';
16
15
  const getRelativeTime = (timestamp) => {
17
16
  const now = Date.now();
18
17
  const diff = Math.max(0, now - timestamp);
@@ -424,28 +423,6 @@ const CrashTab = React.memo(() => {
424
423
  </TouchableScale>)}
425
424
  </ScrollView>) : (<>
426
425
  <FlatList ref={listRef} data={filteredList} keyExtractor={item => item.id} renderItem={renderCard} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} contentContainerStyle={localStyles.listContent} showsVerticalScrollIndicator={false} ListFooterComponent={<EndOfListFooter />}/>
427
-
428
- <TouchableScale onPress={() => {
429
- try {
430
- listRef.current?.scrollToOffset({
431
- offset: 0,
432
- animated: true,
433
- });
434
- }
435
- catch {
436
- try {
437
- listRef.current?.scrollToIndex({
438
- index: 0,
439
- animated: true,
440
- });
441
- }
442
- catch { }
443
- }
444
- }} hitSlop={12} style={styles.scrollTopBtn}>
445
- <View style={{ transform: [{ rotate: '180deg' }] }}>
446
- <ChevronIcon color={AppColors.white} size={18}/>
447
- </View>
448
- </TouchableScale>
449
426
  </>)}
450
427
 
451
428
 
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ export interface FeedbackModalProps {
3
+ visible?: boolean;
4
+ onClose: () => void;
5
+ }
6
+ export declare const FeedbackModal: React.FC<FeedbackModalProps>;
7
+ export default FeedbackModal;
@@ -0,0 +1,515 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Alert, Dimensions, PixelRatio, Platform, Linking, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native';
3
+ import { AppColors } from '../../styles/AppColors';
4
+ import { AppFonts } from '../../styles/AppFonts';
5
+ import { LIB_VERSION } from '../../constants/version';
6
+ import { getAppName, getBundleIdentifier, getAppVersionAndBuild, copyToClipboard, showToast, } from '../../helpers';
7
+ import { trackTelemetryEvent } from '../../helpers/telemetry';
8
+ import { HeadphonesIcon, SendIcon, StarIcon, CopyIcon, InfoCircleIcon, CheckIcon, } from '../NetworkIcons';
9
+ const DEVELOPER_EMAIL = 'vengatmacuser@gmail.com';
10
+ const CATEGORIES = [
11
+ { key: 'feedback', label: 'Feedback', icon: '💬' },
12
+ { key: 'bug', label: 'Bug Report', icon: '🐛' },
13
+ { key: 'feature', label: 'Feature Request', icon: '✨' },
14
+ { key: 'performance', label: 'Performance', icon: '⚡' },
15
+ { key: 'help', label: 'Support / Help', icon: '❓' },
16
+ ];
17
+ const RATING_LABELS = ['', 'Poor', 'Fair', 'Good', 'Very Good', 'Excellent!'];
18
+ export const FeedbackModal = ({ onClose }) => {
19
+ const [category, setCategory] = useState('feedback');
20
+ const [rating, setRating] = useState(5);
21
+ const [subject, setSubject] = useState('');
22
+ const [message, setMessage] = useState('');
23
+ const [showDiagnostics, setShowDiagnostics] = useState(false);
24
+ const [isSending, setIsSending] = useState(false);
25
+ const [copiedDraft, setCopiedDraft] = useState(false);
26
+ useEffect(() => {
27
+ trackTelemetryEvent('feedback_modal_opened', { category });
28
+ }, []);
29
+ const appName = getAppName() || 'React Native App';
30
+ const bundleId = getBundleIdentifier() || 'unknown';
31
+ const appVersion = getAppVersionAndBuild() || '1.0.0';
32
+ const { width, height } = Dimensions.get('window');
33
+ const getDiagnosticsText = () => {
34
+ return [
35
+ `• Package: react-native-inapp-inspector@${LIB_VERSION}`,
36
+ `• Platform: ${Platform.OS.toUpperCase()} (${Platform.Version})`,
37
+ `• App: ${appName} (${bundleId}) v${appVersion}`,
38
+ `• Screen: ${Math.round(width)} x ${Math.round(height)} (Scale ${PixelRatio.get()})`,
39
+ `• Timestamp: ${new Date().toISOString()}`,
40
+ ].join('\n');
41
+ };
42
+ const buildEmailDraft = () => {
43
+ const activeCat = CATEGORIES.find(c => c.key === category)?.label || category;
44
+ const emailSubject = `[In-App Inspector] [${activeCat}] ${subject.trim() || 'User Feedback'}`;
45
+ const bodyLines = [
46
+ `Category: ${activeCat}`,
47
+ `Rating: ${rating} / 5 (${RATING_LABELS[rating] || ''})`,
48
+ ...(subject.trim() ? [`Subject: ${subject.trim()}`] : []),
49
+ '',
50
+ 'Feedback / Message:',
51
+ message.trim() || '(No message provided)',
52
+ '',
53
+ '----------------------------------------',
54
+ 'System & Diagnostic Info:',
55
+ getDiagnosticsText(),
56
+ '----------------------------------------',
57
+ ];
58
+ return {
59
+ activeCat,
60
+ subject: emailSubject,
61
+ body: bodyLines.join('\n'),
62
+ };
63
+ };
64
+ const handleCopyDraft = () => {
65
+ const draft = buildEmailDraft();
66
+ const fullText = `To: ${DEVELOPER_EMAIL}\nSubject: ${draft.subject}\n\n${draft.body}`;
67
+ copyToClipboard(fullText, 'Email Draft & Diagnostics');
68
+ setCopiedDraft(true);
69
+ setTimeout(() => setCopiedDraft(false), 2000);
70
+ showToast('Feedback draft copied to clipboard');
71
+ };
72
+ const handleSendEmail = async () => {
73
+ if (isSending)
74
+ return;
75
+ setIsSending(true);
76
+ try {
77
+ const draft = buildEmailDraft();
78
+ const mailtoUrl = `mailto:${DEVELOPER_EMAIL}?subject=${encodeURIComponent(draft.subject)}&body=${encodeURIComponent(draft.body)}`;
79
+ trackTelemetryEvent('feedback_submitted', {
80
+ category,
81
+ rating,
82
+ has_subject: Boolean(subject.trim()),
83
+ has_message: Boolean(message.trim()),
84
+ });
85
+ let opened = false;
86
+ try {
87
+ const canOpen = await Linking.canOpenURL(mailtoUrl);
88
+ if (canOpen) {
89
+ await Linking.openURL(mailtoUrl);
90
+ opened = true;
91
+ }
92
+ else {
93
+ await Linking.openURL(mailtoUrl);
94
+ opened = true;
95
+ }
96
+ }
97
+ catch (err) {
98
+ opened = false;
99
+ }
100
+ if (opened) {
101
+ showToast('Opening mail client...');
102
+ onClose();
103
+ }
104
+ else {
105
+ const fullDraft = `To: ${DEVELOPER_EMAIL}\nSubject: ${draft.subject}\n\n${draft.body}`;
106
+ copyToClipboard(fullDraft, 'Feedback Draft');
107
+ Alert.alert('Email Client Not Available', `Your feedback and diagnostics have been copied to the clipboard!\n\nYou can paste and send directly to:\n${DEVELOPER_EMAIL}`, [
108
+ {
109
+ text: 'Copy Email Address',
110
+ onPress: () => {
111
+ copyToClipboard(DEVELOPER_EMAIL, 'Email Address');
112
+ showToast('Email address copied!');
113
+ },
114
+ },
115
+ {
116
+ text: 'Done',
117
+ style: 'default',
118
+ onPress: onClose,
119
+ },
120
+ ]);
121
+ }
122
+ }
123
+ catch (err) {
124
+ console.warn('[FeedbackModal] Error sending email:', err);
125
+ }
126
+ finally {
127
+ setIsSending(false);
128
+ }
129
+ };
130
+ return (<View style={styles.container}>
131
+
132
+ <View style={styles.bannerBar}>
133
+ <View style={styles.bannerIconBox}>
134
+ <HeadphonesIcon size={18} color={AppColors.purple}/>
135
+ </View>
136
+ <View style={{ flex: 1 }}>
137
+ <Text style={styles.bannerTitle}>Share your feedback or report an issue</Text>
138
+ <Text style={styles.bannerSub}>
139
+ Direct communication with the maintainers. Diagnostic info is auto-attached.
140
+ </Text>
141
+ </View>
142
+ </View>
143
+
144
+ <ScrollView style={styles.scrollContent} contentContainerStyle={styles.scrollInner} showsVerticalScrollIndicator={false} keyboardShouldPersistTaps="handled">
145
+
146
+ <Text style={styles.sectionLabel}>WHAT CAN WE HELP WITH?</Text>
147
+ <View style={styles.categoryGrid}>
148
+ {CATEGORIES.map(cat => {
149
+ const isSelected = category === cat.key;
150
+ return (<TouchableOpacity key={cat.key} activeOpacity={0.7} onPress={() => setCategory(cat.key)} style={[
151
+ styles.categoryChip,
152
+ isSelected && styles.categoryChipSelected,
153
+ ]}>
154
+ <Text style={styles.categoryIcon}>{cat.icon}</Text>
155
+ <Text style={[
156
+ styles.categoryText,
157
+ isSelected && styles.categoryTextSelected,
158
+ ]}>
159
+ {cat.label}
160
+ </Text>
161
+ </TouchableOpacity>);
162
+ })}
163
+ </View>
164
+
165
+
166
+ <View style={styles.ratingCard}>
167
+ <View style={styles.ratingHeader}>
168
+ <Text style={styles.ratingTitle}>Rate your experience</Text>
169
+ {rating > 0 && (<Text style={styles.ratingBadge}>
170
+ {RATING_LABELS[rating]}
171
+ </Text>)}
172
+ </View>
173
+ <View style={styles.starsRow}>
174
+ {[1, 2, 3, 4, 5].map(star => {
175
+ const filled = star <= rating;
176
+ return (<TouchableOpacity key={star} activeOpacity={0.7} onPress={() => setRating(star)} style={styles.starTouch}>
177
+ <StarIcon size={26} color={filled ? '#FFB800' : AppColors.grayTextWeak}/>
178
+ </TouchableOpacity>);
179
+ })}
180
+ </View>
181
+ </View>
182
+
183
+
184
+ <Text style={styles.sectionLabel}>SUBJECT (OPTIONAL)</Text>
185
+ <View style={styles.inputWrapper}>
186
+ <TextInput style={styles.textInput} placeholder="Brief summary..." placeholderTextColor={AppColors.grayTextWeak} value={subject} onChangeText={setSubject} maxLength={80}/>
187
+ </View>
188
+
189
+
190
+ <Text style={styles.sectionLabel}>YOUR MESSAGE / DETAILS</Text>
191
+ <View style={[styles.inputWrapper, styles.textareaWrapper]}>
192
+ <TextInput style={[styles.textInput, styles.textarea]} placeholder="Describe your issue, feature request, or suggestion in detail..." placeholderTextColor={AppColors.grayTextWeak} value={message} onChangeText={setMessage} multiline numberOfLines={4} textAlignVertical="top"/>
193
+ </View>
194
+
195
+
196
+ <TouchableOpacity activeOpacity={0.8} onPress={() => setShowDiagnostics(!showDiagnostics)} style={styles.diagnosticsToggle}>
197
+ <View style={styles.diagnosticsHeader}>
198
+ <InfoCircleIcon size={14} color={AppColors.purple}/>
199
+ <Text style={styles.diagnosticsTitle}>
200
+ Diagnostics & System Details (Auto-attached)
201
+ </Text>
202
+ </View>
203
+ <Text style={styles.diagnosticsArrow}>
204
+ {showDiagnostics ? '▲' : '▼'}
205
+ </Text>
206
+ </TouchableOpacity>
207
+
208
+ {showDiagnostics && (<View style={styles.diagnosticsBox}>
209
+ <Text style={styles.diagnosticsText}>
210
+ {getDiagnosticsText()}
211
+ </Text>
212
+ </View>)}
213
+
214
+
215
+ <View style={styles.recipientBadge}>
216
+ <Text style={styles.recipientText}>
217
+ 📧 Emails to: <Text style={styles.recipientHighlight}>{DEVELOPER_EMAIL}</Text>
218
+ </Text>
219
+ </View>
220
+ </ScrollView>
221
+
222
+
223
+ <View style={styles.footer}>
224
+ <TouchableOpacity activeOpacity={0.7} onPress={handleCopyDraft} style={styles.copyDraftBtn}>
225
+ {copiedDraft ? (<CheckIcon size={14} color={AppColors.emerald500}/>) : (<CopyIcon size={14} color={AppColors.grayText}/>)}
226
+ <Text style={[
227
+ styles.copyDraftBtnText,
228
+ copiedDraft && { color: AppColors.emerald500 },
229
+ ]}>
230
+ {copiedDraft ? 'Copied!' : 'Copy Draft'}
231
+ </Text>
232
+ </TouchableOpacity>
233
+
234
+ <View style={styles.footerRight}>
235
+ <TouchableOpacity activeOpacity={0.7} onPress={onClose} style={styles.cancelBtn}>
236
+ <Text style={styles.cancelBtnText}>Cancel</Text>
237
+ </TouchableOpacity>
238
+
239
+ <TouchableOpacity activeOpacity={0.8} onPress={handleSendEmail} style={styles.submitBtn}>
240
+ <SendIcon size={16} color={AppColors.white}/>
241
+ <Text style={styles.submitBtnText}>
242
+ {isSending ? 'Sending...' : 'Send via Email'}
243
+ </Text>
244
+ </TouchableOpacity>
245
+ </View>
246
+ </View>
247
+ </View>);
248
+ };
249
+ const styles = StyleSheet.create({
250
+ container: {
251
+ flex: 1,
252
+ backgroundColor: AppColors.primaryLight,
253
+ },
254
+ bannerBar: {
255
+ flexDirection: 'row',
256
+ alignItems: 'center',
257
+ gap: 10,
258
+ backgroundColor: `${AppColors.purple}12`,
259
+ paddingHorizontal: 14,
260
+ paddingVertical: 10,
261
+ borderBottomWidth: 1,
262
+ borderBottomColor: `${AppColors.purple}20`,
263
+ },
264
+ bannerIconBox: {
265
+ width: 32,
266
+ height: 32,
267
+ borderRadius: 8,
268
+ backgroundColor: `${AppColors.purple}20`,
269
+ alignItems: 'center',
270
+ justifyContent: 'center',
271
+ },
272
+ bannerTitle: {
273
+ fontFamily: AppFonts.interSemiBold,
274
+ fontSize: 12.5,
275
+ color: AppColors.purple,
276
+ },
277
+ bannerSub: {
278
+ fontFamily: AppFonts.interRegular,
279
+ fontSize: 10.5,
280
+ color: AppColors.grayText,
281
+ marginTop: 1,
282
+ },
283
+ scrollContent: {
284
+ flex: 1,
285
+ },
286
+ scrollInner: {
287
+ paddingHorizontal: 16,
288
+ paddingTop: 12,
289
+ paddingBottom: 20,
290
+ },
291
+ sectionLabel: {
292
+ fontFamily: AppFonts.interSemiBold,
293
+ fontSize: 10.5,
294
+ color: AppColors.grayText,
295
+ letterSpacing: 0.8,
296
+ marginBottom: 8,
297
+ marginTop: 10,
298
+ },
299
+ categoryGrid: {
300
+ flexDirection: 'row',
301
+ flexWrap: 'wrap',
302
+ gap: 8,
303
+ paddingBottom: 4,
304
+ },
305
+ categoryChip: {
306
+ flexDirection: 'row',
307
+ alignItems: 'center',
308
+ gap: 6,
309
+ paddingVertical: 7,
310
+ paddingHorizontal: 12,
311
+ borderRadius: 8,
312
+ backgroundColor: AppColors.grayBackground,
313
+ borderWidth: 1,
314
+ borderColor: AppColors.dividerColor,
315
+ },
316
+ categoryChipSelected: {
317
+ backgroundColor: `${AppColors.purple}15`,
318
+ borderColor: AppColors.purple,
319
+ },
320
+ categoryIcon: {
321
+ fontSize: 13,
322
+ },
323
+ categoryText: {
324
+ fontFamily: AppFonts.interMedium,
325
+ fontSize: 12,
326
+ color: AppColors.grayText,
327
+ },
328
+ categoryTextSelected: {
329
+ color: AppColors.purple,
330
+ fontFamily: AppFonts.interSemiBold,
331
+ },
332
+ ratingCard: {
333
+ backgroundColor: AppColors.grayBackground,
334
+ borderRadius: 12,
335
+ padding: 12,
336
+ marginTop: 12,
337
+ borderWidth: 1,
338
+ borderColor: AppColors.dividerColor,
339
+ },
340
+ ratingHeader: {
341
+ flexDirection: 'row',
342
+ justifyContent: 'space-between',
343
+ alignItems: 'center',
344
+ marginBottom: 8,
345
+ },
346
+ ratingTitle: {
347
+ fontFamily: AppFonts.interSemiBold,
348
+ fontSize: 12.5,
349
+ color: AppColors.primaryBlack,
350
+ },
351
+ ratingBadge: {
352
+ fontFamily: AppFonts.interSemiBold,
353
+ fontSize: 11,
354
+ color: '#D97706',
355
+ backgroundColor: '#FEF3C7',
356
+ paddingHorizontal: 8,
357
+ paddingVertical: 2,
358
+ borderRadius: 6,
359
+ },
360
+ starsRow: {
361
+ flexDirection: 'row',
362
+ alignItems: 'center',
363
+ gap: 12,
364
+ paddingVertical: 4,
365
+ },
366
+ starTouch: {
367
+ padding: 2,
368
+ },
369
+ inputWrapper: {
370
+ backgroundColor: AppColors.grayBackground,
371
+ borderRadius: 10,
372
+ borderWidth: 1,
373
+ borderColor: AppColors.dividerColor,
374
+ paddingHorizontal: 12,
375
+ paddingVertical: 8,
376
+ },
377
+ textareaWrapper: {
378
+ minHeight: 88,
379
+ paddingVertical: 10,
380
+ },
381
+ textInput: {
382
+ fontFamily: AppFonts.interRegular,
383
+ fontSize: 13,
384
+ color: AppColors.primaryBlack,
385
+ padding: 0,
386
+ },
387
+ textarea: {
388
+ height: 72,
389
+ },
390
+ diagnosticsToggle: {
391
+ flexDirection: 'row',
392
+ alignItems: 'center',
393
+ justifyContent: 'space-between',
394
+ backgroundColor: `${AppColors.purple}10`,
395
+ borderRadius: 8,
396
+ paddingHorizontal: 12,
397
+ paddingVertical: 9,
398
+ marginTop: 14,
399
+ borderWidth: 1,
400
+ borderColor: `${AppColors.purple}25`,
401
+ },
402
+ diagnosticsHeader: {
403
+ flexDirection: 'row',
404
+ alignItems: 'center',
405
+ gap: 8,
406
+ flex: 1,
407
+ },
408
+ diagnosticsTitle: {
409
+ fontFamily: AppFonts.interMedium,
410
+ fontSize: 11.5,
411
+ color: AppColors.purple,
412
+ },
413
+ diagnosticsArrow: {
414
+ fontSize: 10,
415
+ color: AppColors.purple,
416
+ },
417
+ diagnosticsBox: {
418
+ backgroundColor: '#1E1B2E',
419
+ borderRadius: 8,
420
+ padding: 10,
421
+ marginTop: 6,
422
+ borderWidth: 1,
423
+ borderColor: AppColors.dividerColor,
424
+ },
425
+ diagnosticsText: {
426
+ fontFamily: AppFonts.interRegular,
427
+ fontSize: 11,
428
+ color: '#E2DFEB',
429
+ lineHeight: 17,
430
+ },
431
+ recipientBadge: {
432
+ marginTop: 12,
433
+ paddingVertical: 6,
434
+ paddingHorizontal: 10,
435
+ borderRadius: 6,
436
+ backgroundColor: AppColors.grayBackground,
437
+ borderWidth: 1,
438
+ borderColor: AppColors.dividerColor,
439
+ alignSelf: 'flex-start',
440
+ },
441
+ recipientText: {
442
+ fontFamily: AppFonts.interRegular,
443
+ fontSize: 11,
444
+ color: AppColors.grayText,
445
+ },
446
+ recipientHighlight: {
447
+ fontFamily: AppFonts.interSemiBold,
448
+ color: AppColors.purple,
449
+ },
450
+ footer: {
451
+ flexDirection: 'row',
452
+ alignItems: 'center',
453
+ justifyContent: 'space-between',
454
+ paddingHorizontal: 14,
455
+ paddingTop: 10,
456
+ paddingBottom: 14,
457
+ borderTopWidth: 1,
458
+ borderTopColor: AppColors.dividerColor,
459
+ backgroundColor: AppColors.primaryLight,
460
+ },
461
+ copyDraftBtn: {
462
+ flexDirection: 'row',
463
+ alignItems: 'center',
464
+ gap: 5,
465
+ paddingVertical: 8,
466
+ paddingHorizontal: 10,
467
+ borderRadius: 8,
468
+ backgroundColor: AppColors.grayBackground,
469
+ borderWidth: 1,
470
+ borderColor: AppColors.dividerColor,
471
+ },
472
+ copyDraftBtnText: {
473
+ fontFamily: AppFonts.interMedium,
474
+ fontSize: 11.5,
475
+ color: AppColors.grayText,
476
+ },
477
+ footerRight: {
478
+ flexDirection: 'row',
479
+ alignItems: 'center',
480
+ gap: 8,
481
+ },
482
+ cancelBtn: {
483
+ paddingVertical: 8,
484
+ paddingHorizontal: 14,
485
+ borderRadius: 8,
486
+ backgroundColor: AppColors.grayBackground,
487
+ borderWidth: 1,
488
+ borderColor: AppColors.dividerColor,
489
+ },
490
+ cancelBtnText: {
491
+ fontFamily: AppFonts.interMedium,
492
+ fontSize: 12.5,
493
+ color: AppColors.grayText,
494
+ },
495
+ submitBtn: {
496
+ flexDirection: 'row',
497
+ alignItems: 'center',
498
+ gap: 6,
499
+ backgroundColor: AppColors.purple,
500
+ paddingVertical: 8,
501
+ paddingHorizontal: 15,
502
+ borderRadius: 8,
503
+ shadowColor: AppColors.purple,
504
+ shadowOffset: { width: 0, height: 2 },
505
+ shadowOpacity: 0.35,
506
+ shadowRadius: 6,
507
+ elevation: 4,
508
+ },
509
+ submitBtnText: {
510
+ fontFamily: AppFonts.interSemiBold,
511
+ fontSize: 12.5,
512
+ color: AppColors.white,
513
+ },
514
+ });
515
+ export default FeedbackModal;