@mindedsolutions/bug-reporter-sdk 0.1.2 → 0.3.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.
@@ -12,18 +12,24 @@ const ScreenshotPreview_1 = require("./ScreenshotPreview");
12
12
  const constants_1 = require("../constants");
13
13
  function ReportModal() {
14
14
  const { config, translations, isModalVisible, closeModal } = (0, useBugReporter_1.useBugReporter)();
15
- const { captureScreenshot, uploadScreenshot } = (0, useScreenCapture_1.useScreenCapture)();
15
+ const { captureAndUpload } = (0, useScreenCapture_1.useScreenCapture)();
16
16
  const { getDeviceInfo, getAppInfo, getNetworkInfo } = (0, useDeviceInfo_1.useDeviceInfo)();
17
17
  const [description, setDescription] = (0, react_1.useState)('');
18
18
  const [category, setCategory] = (0, react_1.useState)(config.defaultCategory ?? 'Bug');
19
19
  const [severity, setSeverity] = (0, react_1.useState)();
20
20
  const [screenshotUri, setScreenshotUri] = (0, react_1.useState)(null);
21
+ const [screenshotUrl, setScreenshotUrl] = (0, react_1.useState)(null);
21
22
  const [isSubmitting, setIsSubmitting] = (0, react_1.useState)(false);
22
23
  const categories = config.categories ?? constants_1.DEFAULT_CATEGORIES;
23
- // Capture screenshot when modal opens
24
+ // Capture screenshot and upload when modal opens
24
25
  (0, react_1.useEffect)(() => {
25
26
  if (isModalVisible) {
26
- captureScreenshot().then(setScreenshotUri);
27
+ captureAndUpload().then((result) => {
28
+ if (result) {
29
+ setScreenshotUri(result.uri);
30
+ setScreenshotUrl(result.url || null);
31
+ }
32
+ });
27
33
  }
28
34
  else {
29
35
  // Reset form when modal closes
@@ -31,6 +37,7 @@ function ReportModal() {
31
37
  setCategory(config.defaultCategory ?? 'Bug');
32
38
  setSeverity(undefined);
33
39
  setScreenshotUri(null);
40
+ setScreenshotUrl(null);
34
41
  setIsSubmitting(false);
35
42
  }
36
43
  }, [isModalVisible]);
@@ -42,17 +49,11 @@ function ReportModal() {
42
49
  const device = getDeviceInfo();
43
50
  const app = getAppInfo();
44
51
  const network = await getNetworkInfo();
45
- let screenshotUrl;
46
- if (screenshotUri) {
47
- const url = await uploadScreenshot(screenshotUri);
48
- if (url)
49
- screenshotUrl = url;
50
- }
51
52
  const payload = {
52
53
  description: description.trim(),
53
54
  category,
54
55
  severity,
55
- screenshot_url: screenshotUrl,
56
+ screenshot_url: screenshotUrl || undefined,
56
57
  device_brand: device.brand,
57
58
  device_model: device.model,
58
59
  device_os: device.os,
@@ -82,7 +83,7 @@ function ReportModal() {
82
83
  finally {
83
84
  setIsSubmitting(false);
84
85
  }
85
- }, [description, category, severity, screenshotUri, config, translations, closeModal]);
86
+ }, [description, category, severity, screenshotUrl, config, translations, closeModal]);
86
87
  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) => {
87
88
  const labels = {
88
89
  low: translations.severityLow,
@@ -1,4 +1,6 @@
1
1
  export declare function useScreenCapture(): {
2
- captureScreenshot: () => Promise<string | null>;
3
- uploadScreenshot: (uri: string) => Promise<string | null>;
2
+ captureAndUpload: () => Promise<{
3
+ uri: string;
4
+ url: string;
5
+ } | null>;
4
6
  };
@@ -6,45 +6,62 @@ const react_native_view_shot_1 = require("react-native-view-shot");
6
6
  const supabase_js_1 = require("@supabase/supabase-js");
7
7
  const useBugReporter_1 = require("./useBugReporter");
8
8
  const constants_1 = require("../constants");
9
+ function base64ToArrayBuffer(base64) {
10
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
11
+ const clean = base64.replace(/[^A-Za-z0-9+/]/g, '');
12
+ const len = clean.length;
13
+ const byteLen = (len * 3) / 4 - (base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0);
14
+ const buffer = new ArrayBuffer(byteLen);
15
+ const bytes = new Uint8Array(buffer);
16
+ let p = 0;
17
+ for (let i = 0; i < len; i += 4) {
18
+ const a = chars.indexOf(clean[i]);
19
+ const b = chars.indexOf(clean[i + 1]);
20
+ const c = chars.indexOf(clean[i + 2]);
21
+ const d = chars.indexOf(clean[i + 3]);
22
+ const bits = (a << 18) | (b << 12) | (c << 6) | d;
23
+ bytes[p++] = (bits >> 16) & 0xff;
24
+ if (p < byteLen)
25
+ bytes[p++] = (bits >> 8) & 0xff;
26
+ if (p < byteLen)
27
+ bytes[p++] = bits & 0xff;
28
+ }
29
+ return buffer;
30
+ }
9
31
  function useScreenCapture() {
10
32
  const { viewRef, config } = (0, useBugReporter_1.useBugReporter)();
11
- const captureScreenshot = (0, react_1.useCallback)(async () => {
33
+ const captureAndUpload = (0, react_1.useCallback)(async () => {
12
34
  try {
13
35
  if (!viewRef.current)
14
36
  return null;
37
+ // Capture as both URI (for preview) and base64 (for upload)
15
38
  const uri = await (0, react_native_view_shot_1.captureRef)(viewRef.current, {
16
39
  format: constants_1.SCREENSHOT_FORMAT,
17
40
  quality: constants_1.SCREENSHOT_QUALITY,
18
41
  });
19
- return uri;
20
- }
21
- catch {
22
- return null;
23
- }
24
- }, [viewRef, config]);
25
- const uploadScreenshot = (0, react_1.useCallback)(async (uri) => {
26
- try {
42
+ const base64 = await (0, react_native_view_shot_1.captureRef)(viewRef.current, {
43
+ format: constants_1.SCREENSHOT_FORMAT,
44
+ quality: constants_1.SCREENSHOT_QUALITY,
45
+ result: 'base64',
46
+ });
47
+ // Upload base64 decoded to Supabase Storage
27
48
  const supabase = (0, supabase_js_1.createClient)(config.supabaseUrl, config.supabaseAnonKey);
28
49
  const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
29
50
  const fileName = `${config.projectId}/${uniqueId}.jpg`;
30
- // React Native: use FormData with file URI (fetch blob doesn't work reliably)
31
- const formData = new FormData();
32
- formData.append('', {
33
- uri,
34
- name: `${uniqueId}.jpg`,
35
- type: 'image/jpeg',
36
- });
51
+ const arrayBuffer = base64ToArrayBuffer(base64);
37
52
  const { error } = await supabase.storage
38
53
  .from('screenshots')
39
- .upload(fileName, formData, { contentType: 'multipart/form-data' });
40
- if (error)
41
- return null;
54
+ .upload(fileName, arrayBuffer, { contentType: 'image/jpeg' });
55
+ if (error) {
56
+ // Upload failed, still return URI for preview but no remote URL
57
+ return { uri, url: '' };
58
+ }
42
59
  const { data } = supabase.storage.from('screenshots').getPublicUrl(fileName);
43
- return data.publicUrl;
60
+ return { uri, url: data.publicUrl };
44
61
  }
45
62
  catch {
46
63
  return null;
47
64
  }
48
- }, [config]);
49
- return { captureScreenshot, uploadScreenshot };
65
+ }, [viewRef, config]);
66
+ return { captureAndUpload };
50
67
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mindedsolutions/bug-reporter-sdk",
3
- "version": "0.1.2",
4
- "description": "In-app bug reporting SDK for React Native/Expo with shake detection, screenshot capture, and Supabase integration",
3
+ "version": "0.3.0",
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",
7
7
  "license": "MIT",