@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.
@@ -0,0 +1,5 @@
1
+ interface Props {
2
+ onPress: () => void;
3
+ }
4
+ export declare function FloatingButton({ onPress }: Props): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -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,7 @@
1
+ interface Props {
2
+ uri: string;
3
+ onRemove: () => void;
4
+ removeLabel: string;
5
+ }
6
+ export declare function ScreenshotPreview({ uri, onRemove, removeLabel }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -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,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BugReporterContext = void 0;
4
+ const react_1 = require("react");
5
+ exports.BugReporterContext = (0, react_1.createContext)(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,2 @@
1
+ import { BugReporterContextValue } from '../context/BugReporterContext';
2
+ export declare function useBugReporter(): BugReporterContextValue;
@@ -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,6 @@
1
+ import type { DeviceInfo, AppInfo, NetworkInfo } from '../types';
2
+ export declare function useDeviceInfo(): {
3
+ getDeviceInfo: () => DeviceInfo;
4
+ getAppInfo: () => AppInfo;
5
+ getNetworkInfo: () => Promise<NetworkInfo>;
6
+ };
@@ -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,4 @@
1
+ export declare function useScreenCapture(): {
2
+ captureScreenshot: () => Promise<string | null>;
3
+ uploadScreenshot: (uri: string) => Promise<string | null>;
4
+ };
@@ -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,7 @@
1
+ interface UseShakeDetectionOptions {
2
+ enabled: boolean;
3
+ threshold?: number;
4
+ onShake: () => void;
5
+ }
6
+ export declare function useShakeDetection({ enabled, threshold, onShake }: UseShakeDetectionOptions): void;
7
+ export {};
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ import { Translations } from '../types';
2
+ export declare const en: Translations;
@@ -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
+ };
@@ -0,0 +1,2 @@
1
+ import { Translations } from '../types';
2
+ export declare const fr: Translations;
@@ -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,2 @@
1
+ import { SupportedLocale, Translations } from '../types';
2
+ export declare function getTranslations(locale?: SupportedLocale): Translations;
@@ -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
+ }
@@ -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; } });
@@ -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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ }