@mindedsolutions/bug-reporter-sdk 0.3.10 → 0.4.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.
|
@@ -1,15 +1,50 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.ReportModal = ReportModal;
|
|
4
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
38
|
const react_1 = require("react");
|
|
6
39
|
const react_native_1 = require("react-native");
|
|
7
40
|
const supabase_js_1 = require("@supabase/supabase-js");
|
|
41
|
+
const ImagePicker = __importStar(require("expo-image-picker"));
|
|
8
42
|
const useBugReporter_1 = require("../hooks/useBugReporter");
|
|
9
43
|
const useScreenCapture_1 = require("../hooks/useScreenCapture");
|
|
10
44
|
const useDeviceInfo_1 = require("../hooks/useDeviceInfo");
|
|
11
45
|
const ScreenshotPreview_1 = require("./ScreenshotPreview");
|
|
12
46
|
const constants_1 = require("../constants");
|
|
47
|
+
const base64_1 = require("../utils/base64");
|
|
13
48
|
function ReportModal() {
|
|
14
49
|
const { config, translations, isModalVisible, closeModal, pendingScreenshot } = (0, useBugReporter_1.useBugReporter)();
|
|
15
50
|
const { captureAndUpload } = (0, useScreenCapture_1.useScreenCapture)();
|
|
@@ -47,6 +82,36 @@ function ReportModal() {
|
|
|
47
82
|
setIsSubmitting(false);
|
|
48
83
|
}
|
|
49
84
|
}, [isModalVisible]);
|
|
85
|
+
const handlePickImage = (0, react_1.useCallback)(async () => {
|
|
86
|
+
const result = await ImagePicker.launchImageLibraryAsync({
|
|
87
|
+
mediaTypes: ['images'],
|
|
88
|
+
quality: 0.7,
|
|
89
|
+
base64: true,
|
|
90
|
+
});
|
|
91
|
+
if (result.canceled || !result.assets[0])
|
|
92
|
+
return;
|
|
93
|
+
const asset = result.assets[0];
|
|
94
|
+
setScreenshotUri(asset.uri);
|
|
95
|
+
// Upload to Supabase Storage if base64 is available
|
|
96
|
+
if (asset.base64) {
|
|
97
|
+
try {
|
|
98
|
+
const supabase = (0, supabase_js_1.createClient)(config.supabaseUrl, config.supabaseAnonKey);
|
|
99
|
+
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
100
|
+
const fileName = `${config.projectId}/${uniqueId}.jpg`;
|
|
101
|
+
const arrayBuffer = (0, base64_1.base64ToArrayBuffer)(asset.base64);
|
|
102
|
+
const { error } = await supabase.storage
|
|
103
|
+
.from('screenshots')
|
|
104
|
+
.upload(fileName, arrayBuffer, { contentType: 'image/jpeg' });
|
|
105
|
+
if (!error) {
|
|
106
|
+
const { data } = supabase.storage.from('screenshots').getPublicUrl(fileName);
|
|
107
|
+
setScreenshotUrl(data.publicUrl);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Upload failed, URI still available for preview
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}, [config]);
|
|
50
115
|
const handleSubmit = (0, react_1.useCallback)(async () => {
|
|
51
116
|
if (!description.trim())
|
|
52
117
|
return;
|
|
@@ -90,7 +155,7 @@ function ReportModal() {
|
|
|
90
155
|
setIsSubmitting(false);
|
|
91
156
|
}
|
|
92
157
|
}, [description, category, severity, screenshotUrl, config, translations, closeModal]);
|
|
93
|
-
return ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: isModalVisible, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: closeModal, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.container, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.safeTop }), (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: [
|
|
158
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Modal, { visible: isModalVisible, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: closeModal, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.container, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.safeTop }), (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: [(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 }), screenshotUri ? ((0, jsx_runtime_1.jsx)(ScreenshotPreview_1.ScreenshotPreview, { uri: screenshotUri, onRemove: () => { setScreenshotUri(null); setScreenshotUrl(null); }, removeLabel: translations.removeScreenshot })) : null, (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: styles.addImageButton, onPress: handlePickImage, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.addImageText, children: translations.addImage }) })] }), (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) => {
|
|
94
159
|
const labels = {
|
|
95
160
|
low: translations.severityLow,
|
|
96
161
|
medium: translations.severityMedium,
|
|
@@ -189,4 +254,19 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
189
254
|
chipTextActive: {
|
|
190
255
|
color: '#fff',
|
|
191
256
|
},
|
|
257
|
+
addImageButton: {
|
|
258
|
+
marginTop: 8,
|
|
259
|
+
paddingVertical: 10,
|
|
260
|
+
paddingHorizontal: 16,
|
|
261
|
+
borderRadius: 8,
|
|
262
|
+
borderWidth: 1,
|
|
263
|
+
borderColor: '#6366f1',
|
|
264
|
+
borderStyle: 'dashed',
|
|
265
|
+
alignItems: 'center',
|
|
266
|
+
},
|
|
267
|
+
addImageText: {
|
|
268
|
+
fontSize: 14,
|
|
269
|
+
color: '#6366f1',
|
|
270
|
+
fontWeight: '500',
|
|
271
|
+
},
|
|
192
272
|
});
|
|
@@ -28,35 +28,33 @@ function BugReporterProvider({ config, children }) {
|
|
|
28
28
|
const openBoard = (0, react_1.useCallback)(() => setIsBoardVisible(true), []);
|
|
29
29
|
const closeBoard = (0, react_1.useCallback)(() => setIsBoardVisible(false), []);
|
|
30
30
|
// Capture screenshot BEFORE opening modal to avoid iOS crash.
|
|
31
|
-
//
|
|
32
|
-
//
|
|
31
|
+
// Uses captureScreen to capture actual displayed pixels instead of view hierarchy,
|
|
32
|
+
// which avoids capturing the wrong screen in stack navigators.
|
|
33
33
|
const captureAndOpenModal = (0, react_1.useCallback)(async () => {
|
|
34
34
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
setPendingScreenshot({ uri, url: data.publicUrl });
|
|
59
|
-
}
|
|
35
|
+
const uri = await (0, react_native_view_shot_1.captureScreen)({
|
|
36
|
+
format: constants_1.SCREENSHOT_FORMAT,
|
|
37
|
+
quality: constants_1.SCREENSHOT_QUALITY,
|
|
38
|
+
});
|
|
39
|
+
const base64 = await (0, react_native_view_shot_1.captureScreen)({
|
|
40
|
+
format: constants_1.SCREENSHOT_FORMAT,
|
|
41
|
+
quality: constants_1.SCREENSHOT_QUALITY,
|
|
42
|
+
result: 'base64',
|
|
43
|
+
});
|
|
44
|
+
// Upload to Supabase Storage
|
|
45
|
+
const supabase = (0, supabase_js_1.createClient)(config.supabaseUrl, config.supabaseAnonKey);
|
|
46
|
+
const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
47
|
+
const fileName = `${config.projectId}/${uniqueId}.jpg`;
|
|
48
|
+
const arrayBuffer = (0, base64_1.base64ToArrayBuffer)(base64);
|
|
49
|
+
const { error } = await supabase.storage
|
|
50
|
+
.from('screenshots')
|
|
51
|
+
.upload(fileName, arrayBuffer, { contentType: 'image/jpeg' });
|
|
52
|
+
if (error) {
|
|
53
|
+
setPendingScreenshot({ uri, url: '' });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const { data } = supabase.storage.from('screenshots').getPublicUrl(fileName);
|
|
57
|
+
setPendingScreenshot({ uri, url: data.publicUrl });
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
catch {
|
|
@@ -8,17 +8,17 @@ const useBugReporter_1 = require("./useBugReporter");
|
|
|
8
8
|
const constants_1 = require("../constants");
|
|
9
9
|
const base64_1 = require("../utils/base64");
|
|
10
10
|
function useScreenCapture() {
|
|
11
|
-
const {
|
|
11
|
+
const { config } = (0, useBugReporter_1.useBugReporter)();
|
|
12
12
|
const captureAndUpload = (0, react_1.useCallback)(async () => {
|
|
13
13
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
const uri = await (0, react_native_view_shot_1.
|
|
14
|
+
// Use captureScreen to capture the actual displayed screen pixels
|
|
15
|
+
// instead of captureRef which captures the view hierarchy and can
|
|
16
|
+
// show the wrong screen in stack navigators
|
|
17
|
+
const uri = await (0, react_native_view_shot_1.captureScreen)({
|
|
18
18
|
format: constants_1.SCREENSHOT_FORMAT,
|
|
19
19
|
quality: constants_1.SCREENSHOT_QUALITY,
|
|
20
20
|
});
|
|
21
|
-
const base64 = await (0, react_native_view_shot_1.
|
|
21
|
+
const base64 = await (0, react_native_view_shot_1.captureScreen)({
|
|
22
22
|
format: constants_1.SCREENSHOT_FORMAT,
|
|
23
23
|
quality: constants_1.SCREENSHOT_QUALITY,
|
|
24
24
|
result: 'base64',
|
|
@@ -41,6 +41,6 @@ function useScreenCapture() {
|
|
|
41
41
|
catch {
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
|
-
}, [
|
|
44
|
+
}, [config]);
|
|
45
45
|
return { captureAndUpload };
|
|
46
46
|
}
|
package/dist/i18n/en.js
CHANGED
package/dist/i18n/fr.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ViewStyle } from 'react-native';
|
|
|
2
2
|
type ViewStyleProp = ViewStyle;
|
|
3
3
|
export type BugCategory = 'Bug' | 'Crash' | 'UI' | 'Performance' | 'Feature Request' | 'Other';
|
|
4
4
|
export type BugSeverity = 'low' | 'medium' | 'high' | 'critical';
|
|
5
|
-
export type BugStatus = 'open' | 'in_progress' | 'resolved' | 'closed';
|
|
5
|
+
export type BugStatus = 'open' | 'in_progress' | 'resolved' | 'test' | 'closed';
|
|
6
6
|
export type SupportedLocale = 'en' | 'fr';
|
|
7
7
|
export type FeatureCategory = 'UI/UX' | 'Performance' | 'New Feature' | 'Integration' | 'Improvement' | 'Other';
|
|
8
8
|
export type FeatureStatus = 'under_review' | 'planned' | 'in_progress' | 'completed' | 'declined';
|
|
@@ -79,6 +79,7 @@ export interface Translations {
|
|
|
79
79
|
severityMedium: string;
|
|
80
80
|
severityHigh: string;
|
|
81
81
|
severityCritical: string;
|
|
82
|
+
addImage: string;
|
|
82
83
|
featureBoard: string;
|
|
83
84
|
suggestFeature: string;
|
|
84
85
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindedsolutions/bug-reporter-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "In-app bug reporting and feature request SDK for React Native/Expo with shake detection, screenshot capture, feature board, and Supabase integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"expo-application": ">=5 <8",
|
|
25
25
|
"expo-device": ">=6 <9",
|
|
26
|
+
"expo-image-picker": ">=15 <17",
|
|
26
27
|
"expo-network": ">=6 <9",
|
|
27
28
|
"expo-sensors": ">=13 <17",
|
|
28
29
|
"react": ">=18",
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@types/react": "^18",
|
|
38
39
|
"@types/react-native": "^0.72",
|
|
40
|
+
"expo-image-picker": "^55.0.14",
|
|
39
41
|
"typescript": "^5.5.0"
|
|
40
42
|
}
|
|
41
43
|
}
|