@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: [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) => {
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
- // drawViewHierarchyInRect:afterScreenUpdates: crashes when the view hierarchy
32
- // is in transition (modal animating). Capturing first keeps the hierarchy stable.
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
- if (viewRef.current) {
36
- const uri = await (0, react_native_view_shot_1.captureRef)(viewRef.current, {
37
- format: constants_1.SCREENSHOT_FORMAT,
38
- quality: constants_1.SCREENSHOT_QUALITY,
39
- });
40
- const base64 = await (0, react_native_view_shot_1.captureRef)(viewRef.current, {
41
- format: constants_1.SCREENSHOT_FORMAT,
42
- quality: constants_1.SCREENSHOT_QUALITY,
43
- result: 'base64',
44
- });
45
- // Upload to Supabase Storage
46
- const supabase = (0, supabase_js_1.createClient)(config.supabaseUrl, config.supabaseAnonKey);
47
- const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
48
- const fileName = `${config.projectId}/${uniqueId}.jpg`;
49
- const arrayBuffer = (0, base64_1.base64ToArrayBuffer)(base64);
50
- const { error } = await supabase.storage
51
- .from('screenshots')
52
- .upload(fileName, arrayBuffer, { contentType: 'image/jpeg' });
53
- if (error) {
54
- setPendingScreenshot({ uri, url: '' });
55
- }
56
- else {
57
- const { data } = supabase.storage.from('screenshots').getPublicUrl(fileName);
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 { viewRef, config } = (0, useBugReporter_1.useBugReporter)();
11
+ const { config } = (0, useBugReporter_1.useBugReporter)();
12
12
  const captureAndUpload = (0, react_1.useCallback)(async () => {
13
13
  try {
14
- if (!viewRef.current)
15
- return null;
16
- // Capture as both URI (for preview) and base64 (for upload)
17
- const uri = await (0, react_native_view_shot_1.captureRef)(viewRef.current, {
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.captureRef)(viewRef.current, {
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
- }, [viewRef, config]);
44
+ }, [config]);
45
45
  return { captureAndUpload };
46
46
  }
package/dist/i18n/en.js CHANGED
@@ -19,6 +19,7 @@ exports.en = {
19
19
  severityMedium: 'Medium',
20
20
  severityHigh: 'High',
21
21
  severityCritical: 'Critical',
22
+ addImage: 'Add Image',
22
23
  featureBoard: 'Feature Board',
23
24
  suggestFeature: 'Suggest a Feature',
24
25
  };
package/dist/i18n/fr.js CHANGED
@@ -19,6 +19,7 @@ exports.fr = {
19
19
  severityMedium: 'Moyen',
20
20
  severityHigh: 'Élevé',
21
21
  severityCritical: 'Critique',
22
+ addImage: 'Ajouter une image',
22
23
  featureBoard: 'Tableau des fonctionnalités',
23
24
  suggestFeature: 'Suggérer une fonctionnalité',
24
25
  };
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.10",
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
  }