@mindedsolutions/bug-reporter-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/FloatingButton.d.ts +5 -0
- package/dist/components/FloatingButton.js +29 -0
- package/dist/components/ReportModal.d.ts +1 -0
- package/dist/components/ReportModal.js +181 -0
- package/dist/components/ScreenshotPreview.d.ts +7 -0
- package/dist/components/ScreenshotPreview.js +32 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +11 -0
- package/dist/context/BugReporterContext.d.ts +12 -0
- package/dist/context/BugReporterContext.js +5 -0
- package/dist/context/BugReporterProvider.d.ts +8 -0
- package/dist/context/BugReporterProvider.js +27 -0
- package/dist/hooks/useBugReporter.d.ts +2 -0
- package/dist/hooks/useBugReporter.js +12 -0
- package/dist/hooks/useDeviceInfo.d.ts +6 -0
- package/dist/hooks/useDeviceInfo.js +70 -0
- package/dist/hooks/useScreenCapture.d.ts +4 -0
- package/dist/hooks/useScreenCapture.js +45 -0
- package/dist/hooks/useShakeDetection.d.ts +7 -0
- package/dist/hooks/useShakeDetection.js +36 -0
- package/dist/i18n/en.d.ts +2 -0
- package/dist/i18n/en.js +22 -0
- package/dist/i18n/fr.d.ts +2 -0
- package/dist/i18n/fr.js +22 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.js +9 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.js +2 -0
- package/dist/utils/deepMerge.d.ts +1 -0
- package/dist/utils/deepMerge.js +22 -0
- package/package.json +40 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FloatingButton = FloatingButton;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_native_1 = require("react-native");
|
|
6
|
+
function FloatingButton({ onPress }) {
|
|
7
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.button, onPress: onPress, activeOpacity: 0.8, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.icon, children: "\uD83D\uDC1B" }) }));
|
|
8
|
+
}
|
|
9
|
+
const styles = react_native_1.StyleSheet.create({
|
|
10
|
+
button: {
|
|
11
|
+
position: 'absolute',
|
|
12
|
+
bottom: 40,
|
|
13
|
+
right: 20,
|
|
14
|
+
width: 56,
|
|
15
|
+
height: 56,
|
|
16
|
+
borderRadius: 28,
|
|
17
|
+
backgroundColor: '#6366f1',
|
|
18
|
+
justifyContent: 'center',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
elevation: 6,
|
|
21
|
+
shadowColor: '#000',
|
|
22
|
+
shadowOffset: { width: 0, height: 3 },
|
|
23
|
+
shadowOpacity: 0.27,
|
|
24
|
+
shadowRadius: 4.65,
|
|
25
|
+
},
|
|
26
|
+
icon: {
|
|
27
|
+
fontSize: 24,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ReportModal(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReportModal = ReportModal;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const react_native_1 = require("react-native");
|
|
7
|
+
const supabase_js_1 = require("@supabase/supabase-js");
|
|
8
|
+
const useBugReporter_1 = require("../hooks/useBugReporter");
|
|
9
|
+
const useScreenCapture_1 = require("../hooks/useScreenCapture");
|
|
10
|
+
const useDeviceInfo_1 = require("../hooks/useDeviceInfo");
|
|
11
|
+
const ScreenshotPreview_1 = require("./ScreenshotPreview");
|
|
12
|
+
const constants_1 = require("../constants");
|
|
13
|
+
function ReportModal() {
|
|
14
|
+
const { config, translations, isModalVisible, closeModal } = (0, useBugReporter_1.useBugReporter)();
|
|
15
|
+
const { captureScreenshot, uploadScreenshot } = (0, useScreenCapture_1.useScreenCapture)();
|
|
16
|
+
const { getDeviceInfo, getAppInfo, getNetworkInfo } = (0, useDeviceInfo_1.useDeviceInfo)();
|
|
17
|
+
const [description, setDescription] = (0, react_1.useState)('');
|
|
18
|
+
const [category, setCategory] = (0, react_1.useState)(config.defaultCategory ?? 'Bug');
|
|
19
|
+
const [severity, setSeverity] = (0, react_1.useState)();
|
|
20
|
+
const [screenshotUri, setScreenshotUri] = (0, react_1.useState)(null);
|
|
21
|
+
const [isSubmitting, setIsSubmitting] = (0, react_1.useState)(false);
|
|
22
|
+
const categories = config.categories ?? constants_1.DEFAULT_CATEGORIES;
|
|
23
|
+
// Capture screenshot when modal opens
|
|
24
|
+
(0, react_1.useEffect)(() => {
|
|
25
|
+
if (isModalVisible) {
|
|
26
|
+
captureScreenshot().then(setScreenshotUri);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Reset form when modal closes
|
|
30
|
+
setDescription('');
|
|
31
|
+
setCategory(config.defaultCategory ?? 'Bug');
|
|
32
|
+
setSeverity(undefined);
|
|
33
|
+
setScreenshotUri(null);
|
|
34
|
+
setIsSubmitting(false);
|
|
35
|
+
}
|
|
36
|
+
}, [isModalVisible]);
|
|
37
|
+
const handleSubmit = (0, react_1.useCallback)(async () => {
|
|
38
|
+
if (!description.trim())
|
|
39
|
+
return;
|
|
40
|
+
setIsSubmitting(true);
|
|
41
|
+
try {
|
|
42
|
+
const device = getDeviceInfo();
|
|
43
|
+
const app = getAppInfo();
|
|
44
|
+
const network = await getNetworkInfo();
|
|
45
|
+
let screenshotUrl;
|
|
46
|
+
if (screenshotUri) {
|
|
47
|
+
const url = await uploadScreenshot(screenshotUri);
|
|
48
|
+
if (url)
|
|
49
|
+
screenshotUrl = url;
|
|
50
|
+
}
|
|
51
|
+
const payload = {
|
|
52
|
+
description: description.trim(),
|
|
53
|
+
category,
|
|
54
|
+
severity,
|
|
55
|
+
screenshot_url: screenshotUrl,
|
|
56
|
+
device_brand: device.brand,
|
|
57
|
+
device_model: device.model,
|
|
58
|
+
device_os: device.os,
|
|
59
|
+
device_os_version: device.osVersion,
|
|
60
|
+
app_name: app.name,
|
|
61
|
+
app_version: app.version,
|
|
62
|
+
app_build: app.build,
|
|
63
|
+
network_type: network.type,
|
|
64
|
+
network_connected: network.connected,
|
|
65
|
+
current_screen: config.currentScreen,
|
|
66
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
67
|
+
custom_data: config.customData,
|
|
68
|
+
project_id: config.projectId,
|
|
69
|
+
reported_by: config.userId,
|
|
70
|
+
};
|
|
71
|
+
const supabase = (0, supabase_js_1.createClient)(config.supabaseUrl, config.supabaseAnonKey);
|
|
72
|
+
const { error } = await supabase.from('bug_reports').insert(payload);
|
|
73
|
+
if (error)
|
|
74
|
+
throw error;
|
|
75
|
+
config.onReportSubmitted?.(payload);
|
|
76
|
+
react_native_1.Alert.alert('', translations.submitSuccess);
|
|
77
|
+
closeModal();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
react_native_1.Alert.alert('', translations.submitError);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
setIsSubmitting(false);
|
|
84
|
+
}
|
|
85
|
+
}, [description, category, severity, screenshotUri, config, translations, closeModal]);
|
|
86
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: isModalVisible, animationType: "slide", presentationStyle: "pageSheet", onRequestClose: closeModal, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.container, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.header, children: [(0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: closeModal, disabled: isSubmitting, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cancelText, children: translations.cancel }) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.title, children: translations.reportBug }), (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { onPress: handleSubmit, disabled: isSubmitting || !description.trim(), children: isSubmitting ? ((0, jsx_runtime_1.jsx)(react_native_1.ActivityIndicator, { size: "small", color: "#6366f1" })) : ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.submitText, !description.trim() && styles.disabledText], children: translations.submit })) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.ScrollView, { style: styles.body, contentContainerStyle: styles.bodyContent, keyboardShouldPersistTaps: "handled", children: [screenshotUri && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.section, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: translations.screenshot }), (0, jsx_runtime_1.jsx)(ScreenshotPreview_1.ScreenshotPreview, { uri: screenshotUri, onRemove: () => setScreenshotUri(null), removeLabel: translations.removeScreenshot })] })), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.section, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: translations.description }), (0, jsx_runtime_1.jsx)(react_native_1.TextInput, { style: styles.textInput, value: description, onChangeText: setDescription, placeholder: translations.descriptionPlaceholder, placeholderTextColor: "#9ca3af", multiline: true, numberOfLines: 4, textAlignVertical: "top" })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.section, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: translations.category }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.chipRow, children: categories.map((cat) => ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [styles.chip, category === cat && styles.chipActive], onPress: () => setCategory(cat), children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.chipText, category === cat && styles.chipTextActive], children: cat }) }, cat))) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.section, children: [(0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.label, children: [translations.severity, " ", (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.optional, children: ["(", translations.optional, ")"] })] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.chipRow, children: constants_1.SEVERITIES.map((sev) => {
|
|
87
|
+
const labels = {
|
|
88
|
+
low: translations.severityLow,
|
|
89
|
+
medium: translations.severityMedium,
|
|
90
|
+
high: translations.severityHigh,
|
|
91
|
+
critical: translations.severityCritical,
|
|
92
|
+
};
|
|
93
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [styles.chip, severity === sev && styles.chipActive], onPress: () => setSeverity(severity === sev ? undefined : sev), children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [styles.chipText, severity === sev && styles.chipTextActive], children: labels[sev] }) }, sev));
|
|
94
|
+
}) })] })] })] }) }));
|
|
95
|
+
}
|
|
96
|
+
const styles = react_native_1.StyleSheet.create({
|
|
97
|
+
container: {
|
|
98
|
+
flex: 1,
|
|
99
|
+
backgroundColor: '#fff',
|
|
100
|
+
},
|
|
101
|
+
header: {
|
|
102
|
+
flexDirection: 'row',
|
|
103
|
+
alignItems: 'center',
|
|
104
|
+
justifyContent: 'space-between',
|
|
105
|
+
paddingHorizontal: 16,
|
|
106
|
+
paddingVertical: 14,
|
|
107
|
+
borderBottomWidth: 1,
|
|
108
|
+
borderBottomColor: '#e5e7eb',
|
|
109
|
+
},
|
|
110
|
+
title: {
|
|
111
|
+
fontSize: 17,
|
|
112
|
+
fontWeight: '600',
|
|
113
|
+
color: '#111827',
|
|
114
|
+
},
|
|
115
|
+
cancelText: {
|
|
116
|
+
fontSize: 16,
|
|
117
|
+
color: '#6b7280',
|
|
118
|
+
},
|
|
119
|
+
submitText: {
|
|
120
|
+
fontSize: 16,
|
|
121
|
+
color: '#6366f1',
|
|
122
|
+
fontWeight: '600',
|
|
123
|
+
},
|
|
124
|
+
disabledText: {
|
|
125
|
+
opacity: 0.4,
|
|
126
|
+
},
|
|
127
|
+
body: {
|
|
128
|
+
flex: 1,
|
|
129
|
+
},
|
|
130
|
+
bodyContent: {
|
|
131
|
+
padding: 16,
|
|
132
|
+
paddingBottom: 40,
|
|
133
|
+
},
|
|
134
|
+
section: {
|
|
135
|
+
marginBottom: 20,
|
|
136
|
+
},
|
|
137
|
+
label: {
|
|
138
|
+
fontSize: 14,
|
|
139
|
+
fontWeight: '600',
|
|
140
|
+
color: '#374151',
|
|
141
|
+
marginBottom: 8,
|
|
142
|
+
},
|
|
143
|
+
optional: {
|
|
144
|
+
fontWeight: '400',
|
|
145
|
+
color: '#9ca3af',
|
|
146
|
+
},
|
|
147
|
+
textInput: {
|
|
148
|
+
borderWidth: 1,
|
|
149
|
+
borderColor: '#d1d5db',
|
|
150
|
+
borderRadius: 8,
|
|
151
|
+
padding: 12,
|
|
152
|
+
fontSize: 15,
|
|
153
|
+
color: '#111827',
|
|
154
|
+
minHeight: 100,
|
|
155
|
+
},
|
|
156
|
+
chipRow: {
|
|
157
|
+
flexDirection: 'row',
|
|
158
|
+
flexWrap: 'wrap',
|
|
159
|
+
gap: 8,
|
|
160
|
+
},
|
|
161
|
+
chip: {
|
|
162
|
+
paddingHorizontal: 14,
|
|
163
|
+
paddingVertical: 8,
|
|
164
|
+
borderRadius: 20,
|
|
165
|
+
backgroundColor: '#f3f4f6',
|
|
166
|
+
borderWidth: 1,
|
|
167
|
+
borderColor: '#e5e7eb',
|
|
168
|
+
},
|
|
169
|
+
chipActive: {
|
|
170
|
+
backgroundColor: '#6366f1',
|
|
171
|
+
borderColor: '#6366f1',
|
|
172
|
+
},
|
|
173
|
+
chipText: {
|
|
174
|
+
fontSize: 13,
|
|
175
|
+
color: '#374151',
|
|
176
|
+
fontWeight: '500',
|
|
177
|
+
},
|
|
178
|
+
chipTextActive: {
|
|
179
|
+
color: '#fff',
|
|
180
|
+
},
|
|
181
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScreenshotPreview = ScreenshotPreview;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_native_1 = require("react-native");
|
|
6
|
+
function ScreenshotPreview({ uri, onRemove, removeLabel }) {
|
|
7
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.container, children: [(0, jsx_runtime_1.jsx)(react_native_1.Image, { source: { uri }, style: styles.image, resizeMode: "contain" }), (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.removeButton, onPress: onRemove, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.removeText, children: removeLabel }) })] }));
|
|
8
|
+
}
|
|
9
|
+
const styles = react_native_1.StyleSheet.create({
|
|
10
|
+
container: {
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
marginVertical: 12,
|
|
13
|
+
},
|
|
14
|
+
image: {
|
|
15
|
+
width: '100%',
|
|
16
|
+
height: 200,
|
|
17
|
+
borderRadius: 8,
|
|
18
|
+
backgroundColor: '#f3f4f6',
|
|
19
|
+
},
|
|
20
|
+
removeButton: {
|
|
21
|
+
marginTop: 8,
|
|
22
|
+
paddingHorizontal: 12,
|
|
23
|
+
paddingVertical: 6,
|
|
24
|
+
borderRadius: 4,
|
|
25
|
+
backgroundColor: '#ef4444',
|
|
26
|
+
},
|
|
27
|
+
removeText: {
|
|
28
|
+
color: '#fff',
|
|
29
|
+
fontSize: 13,
|
|
30
|
+
fontWeight: '600',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BugCategory, BugSeverity } from './types';
|
|
2
|
+
export declare const DEFAULT_SHAKE_THRESHOLD = 1.8;
|
|
3
|
+
export declare const SHAKE_WINDOW_MS = 500;
|
|
4
|
+
export declare const SHAKE_COUNT = 3;
|
|
5
|
+
export declare const SHAKE_COOLDOWN_MS = 2000;
|
|
6
|
+
export declare const DEFAULT_CATEGORIES: BugCategory[];
|
|
7
|
+
export declare const SEVERITIES: BugSeverity[];
|
|
8
|
+
export declare const SCREENSHOT_QUALITY = 0.7;
|
|
9
|
+
export declare const SCREENSHOT_FORMAT: "jpg";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SCREENSHOT_FORMAT = exports.SCREENSHOT_QUALITY = exports.SEVERITIES = exports.DEFAULT_CATEGORIES = exports.SHAKE_COOLDOWN_MS = exports.SHAKE_COUNT = exports.SHAKE_WINDOW_MS = exports.DEFAULT_SHAKE_THRESHOLD = void 0;
|
|
4
|
+
exports.DEFAULT_SHAKE_THRESHOLD = 1.8;
|
|
5
|
+
exports.SHAKE_WINDOW_MS = 500;
|
|
6
|
+
exports.SHAKE_COUNT = 3;
|
|
7
|
+
exports.SHAKE_COOLDOWN_MS = 2000;
|
|
8
|
+
exports.DEFAULT_CATEGORIES = ['Bug', 'Crash', 'UI', 'Performance', 'Feature Request', 'Other'];
|
|
9
|
+
exports.SEVERITIES = ['low', 'medium', 'high', 'critical'];
|
|
10
|
+
exports.SCREENSHOT_QUALITY = 0.7;
|
|
11
|
+
exports.SCREENSHOT_FORMAT = 'jpg';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { View } from 'react-native';
|
|
3
|
+
import type { BugReporterConfig, Translations } from '../types';
|
|
4
|
+
export interface BugReporterContextValue {
|
|
5
|
+
config: BugReporterConfig;
|
|
6
|
+
translations: Translations;
|
|
7
|
+
isModalVisible: boolean;
|
|
8
|
+
openModal: () => void;
|
|
9
|
+
closeModal: () => void;
|
|
10
|
+
viewRef: RefObject<View>;
|
|
11
|
+
}
|
|
12
|
+
export declare const BugReporterContext: import("react").Context<BugReporterContextValue | null>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { BugReporterConfig } from '../types';
|
|
3
|
+
interface Props {
|
|
4
|
+
config: BugReporterConfig;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
export declare function BugReporterProvider({ config, children }: Props): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BugReporterProvider = BugReporterProvider;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const react_native_1 = require("react-native");
|
|
7
|
+
const BugReporterContext_1 = require("./BugReporterContext");
|
|
8
|
+
const ReportModal_1 = require("../components/ReportModal");
|
|
9
|
+
const FloatingButton_1 = require("../components/FloatingButton");
|
|
10
|
+
const useShakeDetection_1 = require("../hooks/useShakeDetection");
|
|
11
|
+
const i18n_1 = require("../i18n");
|
|
12
|
+
function BugReporterProvider({ config, children }) {
|
|
13
|
+
const [isModalVisible, setIsModalVisible] = (0, react_1.useState)(false);
|
|
14
|
+
const viewRef = (0, react_1.useRef)(null);
|
|
15
|
+
const translations = (0, i18n_1.getTranslations)(config.locale);
|
|
16
|
+
const openModal = (0, react_1.useCallback)(() => setIsModalVisible(true), []);
|
|
17
|
+
const closeModal = (0, react_1.useCallback)(() => setIsModalVisible(false), []);
|
|
18
|
+
(0, useShakeDetection_1.useShakeDetection)({
|
|
19
|
+
enabled: config.enableShake !== false,
|
|
20
|
+
threshold: config.shakeThreshold,
|
|
21
|
+
onShake: openModal,
|
|
22
|
+
});
|
|
23
|
+
return ((0, jsx_runtime_1.jsxs)(BugReporterContext_1.BugReporterContext.Provider, { value: { config, translations, isModalVisible, openModal, closeModal, viewRef }, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { ref: viewRef, collapsable: false, style: styles.container, children: children }), config.floatingButton !== false && (0, jsx_runtime_1.jsx)(FloatingButton_1.FloatingButton, { onPress: openModal }), (0, jsx_runtime_1.jsx)(ReportModal_1.ReportModal, {})] }));
|
|
24
|
+
}
|
|
25
|
+
const styles = react_native_1.StyleSheet.create({
|
|
26
|
+
container: { flex: 1 },
|
|
27
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useBugReporter = useBugReporter;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const BugReporterContext_1 = require("../context/BugReporterContext");
|
|
6
|
+
function useBugReporter() {
|
|
7
|
+
const context = (0, react_1.useContext)(BugReporterContext_1.BugReporterContext);
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new Error('useBugReporter must be used within a BugReporterProvider');
|
|
10
|
+
}
|
|
11
|
+
return context;
|
|
12
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.useDeviceInfo = useDeviceInfo;
|
|
37
|
+
const react_1 = require("react");
|
|
38
|
+
const Device = __importStar(require("expo-device"));
|
|
39
|
+
const Application = __importStar(require("expo-application"));
|
|
40
|
+
const Network = __importStar(require("expo-network"));
|
|
41
|
+
function useDeviceInfo() {
|
|
42
|
+
const getDeviceInfo = (0, react_1.useCallback)(() => {
|
|
43
|
+
return {
|
|
44
|
+
brand: Device.brand,
|
|
45
|
+
model: Device.modelName,
|
|
46
|
+
os: Device.osName,
|
|
47
|
+
osVersion: Device.osVersion,
|
|
48
|
+
};
|
|
49
|
+
}, []);
|
|
50
|
+
const getAppInfo = (0, react_1.useCallback)(() => {
|
|
51
|
+
return {
|
|
52
|
+
name: Application.applicationName,
|
|
53
|
+
version: Application.nativeApplicationVersion,
|
|
54
|
+
build: Application.nativeBuildVersion,
|
|
55
|
+
};
|
|
56
|
+
}, []);
|
|
57
|
+
const getNetworkInfo = (0, react_1.useCallback)(async () => {
|
|
58
|
+
try {
|
|
59
|
+
const state = await Network.getNetworkStateAsync();
|
|
60
|
+
return {
|
|
61
|
+
type: state.type ? String(state.type) : null,
|
|
62
|
+
connected: state.isConnected ?? false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return { type: null, connected: false };
|
|
67
|
+
}
|
|
68
|
+
}, []);
|
|
69
|
+
return { getDeviceInfo, getAppInfo, getNetworkInfo };
|
|
70
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useScreenCapture = useScreenCapture;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const react_native_view_shot_1 = require("react-native-view-shot");
|
|
6
|
+
const supabase_js_1 = require("@supabase/supabase-js");
|
|
7
|
+
const useBugReporter_1 = require("./useBugReporter");
|
|
8
|
+
const constants_1 = require("../constants");
|
|
9
|
+
function useScreenCapture() {
|
|
10
|
+
const { viewRef, config } = (0, useBugReporter_1.useBugReporter)();
|
|
11
|
+
const captureScreenshot = (0, react_1.useCallback)(async () => {
|
|
12
|
+
try {
|
|
13
|
+
if (!viewRef.current)
|
|
14
|
+
return null;
|
|
15
|
+
const uri = await (0, react_native_view_shot_1.captureRef)(viewRef.current, {
|
|
16
|
+
format: constants_1.SCREENSHOT_FORMAT,
|
|
17
|
+
quality: constants_1.SCREENSHOT_QUALITY,
|
|
18
|
+
});
|
|
19
|
+
return uri;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}, [viewRef, config]);
|
|
25
|
+
const uploadScreenshot = (0, react_1.useCallback)(async (uri) => {
|
|
26
|
+
try {
|
|
27
|
+
const supabase = (0, supabase_js_1.createClient)(config.supabaseUrl, config.supabaseAnonKey);
|
|
28
|
+
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
29
|
+
const fileName = `${config.projectId}/${uniqueId}.jpg`;
|
|
30
|
+
const response = await fetch(uri);
|
|
31
|
+
const blob = await response.blob();
|
|
32
|
+
const { error } = await supabase.storage
|
|
33
|
+
.from('screenshots')
|
|
34
|
+
.upload(fileName, blob, { contentType: 'image/jpeg' });
|
|
35
|
+
if (error)
|
|
36
|
+
return null;
|
|
37
|
+
const { data } = supabase.storage.from('screenshots').getPublicUrl(fileName);
|
|
38
|
+
return data.publicUrl;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}, [config]);
|
|
44
|
+
return { captureScreenshot, uploadScreenshot };
|
|
45
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useShakeDetection = useShakeDetection;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const expo_sensors_1 = require("expo-sensors");
|
|
6
|
+
const constants_1 = require("../constants");
|
|
7
|
+
function useShakeDetection({ enabled, threshold, onShake }) {
|
|
8
|
+
const shakeThreshold = threshold ?? constants_1.DEFAULT_SHAKE_THRESHOLD;
|
|
9
|
+
const timestamps = (0, react_1.useRef)([]);
|
|
10
|
+
const lastShake = (0, react_1.useRef)(0);
|
|
11
|
+
const onShakeRef = (0, react_1.useRef)(onShake);
|
|
12
|
+
onShakeRef.current = onShake;
|
|
13
|
+
(0, react_1.useEffect)(() => {
|
|
14
|
+
if (!enabled)
|
|
15
|
+
return;
|
|
16
|
+
expo_sensors_1.Accelerometer.setUpdateInterval(100);
|
|
17
|
+
const subscription = expo_sensors_1.Accelerometer.addListener(({ x, y, z }) => {
|
|
18
|
+
const magnitude = Math.sqrt(x * x + y * y + z * z);
|
|
19
|
+
if (magnitude < shakeThreshold)
|
|
20
|
+
return;
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
// Cooldown check
|
|
23
|
+
if (now - lastShake.current < constants_1.SHAKE_COOLDOWN_MS)
|
|
24
|
+
return;
|
|
25
|
+
// Add timestamp and clean old ones
|
|
26
|
+
timestamps.current.push(now);
|
|
27
|
+
timestamps.current = timestamps.current.filter((t) => now - t < constants_1.SHAKE_WINDOW_MS);
|
|
28
|
+
if (timestamps.current.length >= constants_1.SHAKE_COUNT) {
|
|
29
|
+
lastShake.current = now;
|
|
30
|
+
timestamps.current = [];
|
|
31
|
+
onShakeRef.current();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return () => subscription.remove();
|
|
35
|
+
}, [enabled, shakeThreshold]);
|
|
36
|
+
}
|
package/dist/i18n/en.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.en = void 0;
|
|
4
|
+
exports.en = {
|
|
5
|
+
reportBug: 'Report a Bug',
|
|
6
|
+
description: 'Description',
|
|
7
|
+
descriptionPlaceholder: 'Describe the issue...',
|
|
8
|
+
category: 'Category',
|
|
9
|
+
severity: 'Severity',
|
|
10
|
+
submit: 'Submit',
|
|
11
|
+
cancel: 'Cancel',
|
|
12
|
+
submitting: 'Submitting...',
|
|
13
|
+
submitSuccess: 'Bug report submitted successfully!',
|
|
14
|
+
submitError: 'Failed to submit bug report. Please try again.',
|
|
15
|
+
screenshot: 'Screenshot',
|
|
16
|
+
removeScreenshot: 'Remove',
|
|
17
|
+
optional: 'Optional',
|
|
18
|
+
severityLow: 'Low',
|
|
19
|
+
severityMedium: 'Medium',
|
|
20
|
+
severityHigh: 'High',
|
|
21
|
+
severityCritical: 'Critical',
|
|
22
|
+
};
|
package/dist/i18n/fr.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fr = void 0;
|
|
4
|
+
exports.fr = {
|
|
5
|
+
reportBug: 'Signaler un bug',
|
|
6
|
+
description: 'Description',
|
|
7
|
+
descriptionPlaceholder: 'Décrivez le problème...',
|
|
8
|
+
category: 'Catégorie',
|
|
9
|
+
severity: 'Sévérité',
|
|
10
|
+
submit: 'Envoyer',
|
|
11
|
+
cancel: 'Annuler',
|
|
12
|
+
submitting: 'Envoi en cours...',
|
|
13
|
+
submitSuccess: 'Bug signalé avec succès !',
|
|
14
|
+
submitError: "Échec de l'envoi. Veuillez réessayer.",
|
|
15
|
+
screenshot: 'Capture d\'écran',
|
|
16
|
+
removeScreenshot: 'Supprimer',
|
|
17
|
+
optional: 'Optionnel',
|
|
18
|
+
severityLow: 'Faible',
|
|
19
|
+
severityMedium: 'Moyen',
|
|
20
|
+
severityHigh: 'Élevé',
|
|
21
|
+
severityCritical: 'Critique',
|
|
22
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTranslations = getTranslations;
|
|
4
|
+
const en_1 = require("./en");
|
|
5
|
+
const fr_1 = require("./fr");
|
|
6
|
+
const translations = { en: en_1.en, fr: fr_1.fr };
|
|
7
|
+
function getTranslations(locale = 'en') {
|
|
8
|
+
return translations[locale] ?? translations.en;
|
|
9
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { BugReporterProvider } from './context/BugReporterProvider';
|
|
2
|
+
export { useBugReporter } from './hooks/useBugReporter';
|
|
3
|
+
export { FloatingButton } from './components/FloatingButton';
|
|
4
|
+
export type { BugReporterConfig, BugReportPayload, BugCategory, BugSeverity, BugStatus, SupportedLocale, DeviceInfo, AppInfo, NetworkInfo, Translations, } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FloatingButton = exports.useBugReporter = exports.BugReporterProvider = void 0;
|
|
4
|
+
var BugReporterProvider_1 = require("./context/BugReporterProvider");
|
|
5
|
+
Object.defineProperty(exports, "BugReporterProvider", { enumerable: true, get: function () { return BugReporterProvider_1.BugReporterProvider; } });
|
|
6
|
+
var useBugReporter_1 = require("./hooks/useBugReporter");
|
|
7
|
+
Object.defineProperty(exports, "useBugReporter", { enumerable: true, get: function () { return useBugReporter_1.useBugReporter; } });
|
|
8
|
+
var FloatingButton_1 = require("./components/FloatingButton");
|
|
9
|
+
Object.defineProperty(exports, "FloatingButton", { enumerable: true, get: function () { return FloatingButton_1.FloatingButton; } });
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export type BugCategory = 'Bug' | 'Crash' | 'UI' | 'Performance' | 'Feature Request' | 'Other';
|
|
2
|
+
export type BugSeverity = 'low' | 'medium' | 'high' | 'critical';
|
|
3
|
+
export type BugStatus = 'open' | 'in_progress' | 'resolved' | 'closed';
|
|
4
|
+
export type SupportedLocale = 'en' | 'fr';
|
|
5
|
+
export interface DeviceInfo {
|
|
6
|
+
brand: string | null;
|
|
7
|
+
model: string | null;
|
|
8
|
+
os: string | null;
|
|
9
|
+
osVersion: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface AppInfo {
|
|
12
|
+
name: string | null;
|
|
13
|
+
version: string | null;
|
|
14
|
+
build: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface NetworkInfo {
|
|
17
|
+
type: string | null;
|
|
18
|
+
connected: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface BugReportPayload {
|
|
21
|
+
screenshot_url?: string;
|
|
22
|
+
description: string;
|
|
23
|
+
category: BugCategory;
|
|
24
|
+
severity?: BugSeverity;
|
|
25
|
+
device_brand?: string | null;
|
|
26
|
+
device_model?: string | null;
|
|
27
|
+
device_os?: string | null;
|
|
28
|
+
device_os_version?: string | null;
|
|
29
|
+
app_name?: string | null;
|
|
30
|
+
app_version?: string | null;
|
|
31
|
+
app_build?: string | null;
|
|
32
|
+
network_type?: string | null;
|
|
33
|
+
network_connected?: boolean;
|
|
34
|
+
current_screen?: string;
|
|
35
|
+
timezone?: string;
|
|
36
|
+
custom_data?: Record<string, unknown>;
|
|
37
|
+
project_id: string;
|
|
38
|
+
reported_by?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface BugReporterConfig {
|
|
41
|
+
supabaseUrl: string;
|
|
42
|
+
supabaseAnonKey: string;
|
|
43
|
+
projectId: string;
|
|
44
|
+
locale?: SupportedLocale;
|
|
45
|
+
enableShake?: boolean;
|
|
46
|
+
shakeThreshold?: number;
|
|
47
|
+
floatingButton?: boolean;
|
|
48
|
+
categories?: BugCategory[];
|
|
49
|
+
defaultCategory?: BugCategory;
|
|
50
|
+
onReportSubmitted?: (report: BugReportPayload) => void;
|
|
51
|
+
currentScreen?: string;
|
|
52
|
+
userId?: string;
|
|
53
|
+
customData?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
export interface Translations {
|
|
56
|
+
reportBug: string;
|
|
57
|
+
description: string;
|
|
58
|
+
descriptionPlaceholder: string;
|
|
59
|
+
category: string;
|
|
60
|
+
severity: string;
|
|
61
|
+
submit: string;
|
|
62
|
+
cancel: string;
|
|
63
|
+
submitting: string;
|
|
64
|
+
submitSuccess: string;
|
|
65
|
+
submitError: string;
|
|
66
|
+
screenshot: string;
|
|
67
|
+
removeScreenshot: string;
|
|
68
|
+
optional: string;
|
|
69
|
+
severityLow: string;
|
|
70
|
+
severityMedium: string;
|
|
71
|
+
severityHigh: string;
|
|
72
|
+
severityCritical: string;
|
|
73
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deepMerge = deepMerge;
|
|
4
|
+
function deepMerge(target, source) {
|
|
5
|
+
const result = { ...target };
|
|
6
|
+
for (const key in source) {
|
|
7
|
+
const sourceVal = source[key];
|
|
8
|
+
const targetVal = result[key];
|
|
9
|
+
if (sourceVal &&
|
|
10
|
+
typeof sourceVal === 'object' &&
|
|
11
|
+
!Array.isArray(sourceVal) &&
|
|
12
|
+
targetVal &&
|
|
13
|
+
typeof targetVal === 'object' &&
|
|
14
|
+
!Array.isArray(targetVal)) {
|
|
15
|
+
result[key] = deepMerge(targetVal, sourceVal);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
result[key] = sourceVal;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mindedsolutions/bug-reporter-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "In-app bug reporting SDK for React Native/Expo with shake detection, screenshot capture, and Supabase integration",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/etroadec/bug-reporter"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"clean": "rm -rf dist"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"react": ">=18",
|
|
25
|
+
"react-native": ">=0.72",
|
|
26
|
+
"expo-sensors": ">=13",
|
|
27
|
+
"react-native-view-shot": ">=3",
|
|
28
|
+
"expo-device": ">=6",
|
|
29
|
+
"expo-application": ">=5",
|
|
30
|
+
"expo-network": ">=6"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@supabase/supabase-js": "^2.49.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/react": "^18",
|
|
37
|
+
"@types/react-native": "^0.72",
|
|
38
|
+
"typescript": "^5.5.0"
|
|
39
|
+
}
|
|
40
|
+
}
|