@bbearai/react-native 0.4.1 → 0.5.1
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/index.js +83 -10
- package/dist/index.mjs +93 -20
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -11653,6 +11653,7 @@ var HOSTED_BUGBEAR_ANON_KEY = getEnvVar("BUGBEAR_ANON_KEY") || getEnvVar("NEXT_P
|
|
|
11653
11653
|
var BugBearClient = class {
|
|
11654
11654
|
constructor(config) {
|
|
11655
11655
|
this.navigationHistory = [];
|
|
11656
|
+
this.reportSubmitInFlight = false;
|
|
11656
11657
|
this.config = config;
|
|
11657
11658
|
this.supabase = createClient(
|
|
11658
11659
|
config.supabaseUrl || DEFAULT_SUPABASE_URL,
|
|
@@ -11713,6 +11714,10 @@ var BugBearClient = class {
|
|
|
11713
11714
|
* Submit a report
|
|
11714
11715
|
*/
|
|
11715
11716
|
async submitReport(report) {
|
|
11717
|
+
if (this.reportSubmitInFlight) {
|
|
11718
|
+
return { success: false, error: "A report is already being submitted" };
|
|
11719
|
+
}
|
|
11720
|
+
this.reportSubmitInFlight = true;
|
|
11716
11721
|
try {
|
|
11717
11722
|
const validationError = this.validateReport(report);
|
|
11718
11723
|
if (validationError) {
|
|
@@ -11764,6 +11769,8 @@ var BugBearClient = class {
|
|
|
11764
11769
|
} catch (err) {
|
|
11765
11770
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
11766
11771
|
return { success: false, error: message };
|
|
11772
|
+
} finally {
|
|
11773
|
+
this.reportSubmitInFlight = false;
|
|
11767
11774
|
}
|
|
11768
11775
|
}
|
|
11769
11776
|
/**
|
|
@@ -11804,6 +11811,14 @@ var BugBearClient = class {
|
|
|
11804
11811
|
name,
|
|
11805
11812
|
description,
|
|
11806
11813
|
sort_order
|
|
11814
|
+
),
|
|
11815
|
+
role:project_roles(
|
|
11816
|
+
id,
|
|
11817
|
+
name,
|
|
11818
|
+
slug,
|
|
11819
|
+
color,
|
|
11820
|
+
description,
|
|
11821
|
+
login_hint
|
|
11807
11822
|
)
|
|
11808
11823
|
)
|
|
11809
11824
|
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
|
|
@@ -11841,6 +11856,14 @@ var BugBearClient = class {
|
|
|
11841
11856
|
name: item.test_case.group.name,
|
|
11842
11857
|
description: item.test_case.group.description,
|
|
11843
11858
|
sortOrder: item.test_case.group.sort_order
|
|
11859
|
+
} : void 0,
|
|
11860
|
+
role: item.test_case.role ? {
|
|
11861
|
+
id: item.test_case.role.id,
|
|
11862
|
+
name: item.test_case.role.name,
|
|
11863
|
+
slug: item.test_case.role.slug,
|
|
11864
|
+
color: item.test_case.role.color,
|
|
11865
|
+
description: item.test_case.role.description,
|
|
11866
|
+
loginHint: item.test_case.role.login_hint
|
|
11844
11867
|
} : void 0
|
|
11845
11868
|
}
|
|
11846
11869
|
}));
|
|
@@ -12256,8 +12279,8 @@ var BugBearClient = class {
|
|
|
12256
12279
|
return "Maximum 10 screenshots allowed";
|
|
12257
12280
|
}
|
|
12258
12281
|
for (const url of report.screenshots) {
|
|
12259
|
-
if (typeof url !== "string" || url.length > 2e3) {
|
|
12260
|
-
return "Invalid screenshot URL";
|
|
12282
|
+
if (typeof url !== "string" || url.length > 2e3 || !/^https?:\/\//i.test(url)) {
|
|
12283
|
+
return "Invalid screenshot URL (must be an HTTP/HTTPS URL)";
|
|
12261
12284
|
}
|
|
12262
12285
|
}
|
|
12263
12286
|
}
|
|
@@ -12646,7 +12669,10 @@ var BugBearClient = class {
|
|
|
12646
12669
|
content
|
|
12647
12670
|
};
|
|
12648
12671
|
if (attachments && attachments.length > 0) {
|
|
12649
|
-
|
|
12672
|
+
const safeAttachments = attachments.filter((a) => /^https?:\/\//i.test(a.url));
|
|
12673
|
+
if (safeAttachments.length > 0) {
|
|
12674
|
+
insertData.attachments = safeAttachments;
|
|
12675
|
+
}
|
|
12650
12676
|
}
|
|
12651
12677
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
12652
12678
|
if (error) {
|
|
@@ -14021,10 +14047,19 @@ var import_react_native5 = require("react-native");
|
|
|
14021
14047
|
function TestListScreen({ nav }) {
|
|
14022
14048
|
const { assignments, currentAssignment, refreshAssignments } = useBugBear();
|
|
14023
14049
|
const [filter, setFilter] = (0, import_react5.useState)("all");
|
|
14050
|
+
const [roleFilter, setRoleFilter] = (0, import_react5.useState)(null);
|
|
14024
14051
|
const [collapsedFolders, setCollapsedFolders] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
|
|
14025
14052
|
(0, import_react5.useEffect)(() => {
|
|
14026
14053
|
refreshAssignments();
|
|
14027
14054
|
}, []);
|
|
14055
|
+
const availableRoles = (0, import_react5.useMemo)(() => {
|
|
14056
|
+
const roleMap = /* @__PURE__ */ new Map();
|
|
14057
|
+
for (const a of assignments) {
|
|
14058
|
+
if (a.testCase.role) roleMap.set(a.testCase.role.id, a.testCase.role);
|
|
14059
|
+
}
|
|
14060
|
+
return Array.from(roleMap.values());
|
|
14061
|
+
}, [assignments]);
|
|
14062
|
+
const selectedRole = availableRoles.find((r) => r.id === roleFilter);
|
|
14028
14063
|
const groupedAssignments = (0, import_react5.useMemo)(() => {
|
|
14029
14064
|
const groups = /* @__PURE__ */ new Map();
|
|
14030
14065
|
for (const assignment of assignments) {
|
|
@@ -14068,16 +14103,39 @@ function TestListScreen({ nav }) {
|
|
|
14068
14103
|
});
|
|
14069
14104
|
}, []);
|
|
14070
14105
|
const filterAssignment = (a) => {
|
|
14106
|
+
if (roleFilter && a.testCase.role?.id !== roleFilter) return false;
|
|
14071
14107
|
if (filter === "pending") return a.status === "pending" || a.status === "in_progress";
|
|
14072
14108
|
if (filter === "completed") return a.status === "passed" || a.status === "failed";
|
|
14073
14109
|
return true;
|
|
14074
14110
|
};
|
|
14075
|
-
return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, null, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.filterBar }, ["all", "pending", "completed"].map((f) => /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { key: f, style: [styles3.filterBtn, filter === f && styles3.filterBtnActive], onPress: () => setFilter(f) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.filterBtnText, filter === f && styles3.filterBtnTextActive] }, f === "all" ? `All (${assignments.length})` : f === "pending" ? `To Do (${assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length})` : `Done (${assignments.filter((a) => a.status === "passed" || a.status === "failed").length})`)))),
|
|
14111
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, null, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.filterBar }, ["all", "pending", "completed"].map((f) => /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { key: f, style: [styles3.filterBtn, filter === f && styles3.filterBtnActive], onPress: () => setFilter(f) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.filterBtnText, filter === f && styles3.filterBtnTextActive] }, f === "all" ? `All (${assignments.length})` : f === "pending" ? `To Do (${assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length})` : `Done (${assignments.filter((a) => a.status === "passed" || a.status === "failed").length})`)))), availableRoles.length >= 2 && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.roleSection }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.roleBar }, /* @__PURE__ */ import_react5.default.createElement(
|
|
14112
|
+
import_react_native5.TouchableOpacity,
|
|
14113
|
+
{
|
|
14114
|
+
style: [styles3.roleBtn, !roleFilter && styles3.roleBtnActive],
|
|
14115
|
+
onPress: () => setRoleFilter(null)
|
|
14116
|
+
},
|
|
14117
|
+
/* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.roleBtnText, !roleFilter && styles3.roleBtnTextActive] }, "All Roles")
|
|
14118
|
+
), availableRoles.map((role) => {
|
|
14119
|
+
const isActive = roleFilter === role.id;
|
|
14120
|
+
return /* @__PURE__ */ import_react5.default.createElement(
|
|
14121
|
+
import_react_native5.TouchableOpacity,
|
|
14122
|
+
{
|
|
14123
|
+
key: role.id,
|
|
14124
|
+
style: [
|
|
14125
|
+
styles3.roleBtn,
|
|
14126
|
+
isActive && { backgroundColor: role.color + "20", borderColor: role.color + "60", borderWidth: 1 }
|
|
14127
|
+
],
|
|
14128
|
+
onPress: () => setRoleFilter(isActive ? null : role.id)
|
|
14129
|
+
},
|
|
14130
|
+
/* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.roleDot, { backgroundColor: role.color }] }),
|
|
14131
|
+
/* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.roleBtnText, isActive && { color: role.color, fontWeight: "600" }] }, role.name)
|
|
14132
|
+
);
|
|
14133
|
+
})), selectedRole?.loginHint && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.loginHint, { backgroundColor: selectedRole.color + "10", borderColor: selectedRole.color + "30" }] }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.loginHintText }, "Log in as: ", selectedRole.loginHint))), groupedAssignments.map((folder) => {
|
|
14076
14134
|
const folderId = folder.group?.id || "ungrouped";
|
|
14077
14135
|
const isCollapsed = collapsedFolders.has(folderId);
|
|
14078
14136
|
const filtered = folder.assignments.filter(filterAssignment);
|
|
14079
14137
|
if (filtered.length === 0 && filter !== "all") return null;
|
|
14080
|
-
return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { key: folderId, style: styles3.folder }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderName }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.folderProgress }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.folderProgressFill, { width: `${Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100)}%` }] })), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
|
|
14138
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { key: folderId, style: styles3.folder }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderName, numberOfLines: 1 }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.folderProgress }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.folderProgressFill, { width: `${folder.stats.total > 0 ? Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100) : 0}%` }] })), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
|
|
14081
14139
|
const badge = getStatusBadge(assignment.status);
|
|
14082
14140
|
const isCurrent = currentAssignment?.id === assignment.id;
|
|
14083
14141
|
return /* @__PURE__ */ import_react5.default.createElement(
|
|
@@ -14088,17 +14146,28 @@ function TestListScreen({ nav }) {
|
|
|
14088
14146
|
onPress: () => nav.push({ name: "TEST_DETAIL", testId: assignment.id })
|
|
14089
14147
|
},
|
|
14090
14148
|
/* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testBadge }, badge.icon),
|
|
14091
|
-
/* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.testInfo }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.retestTag }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testMeta }, assignment.testCase.
|
|
14149
|
+
/* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.testInfo }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.retestTag }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles3.roleBadgeRow }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.testMeta }, " \xB7 "), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: [styles3.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: [styles3.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name))))
|
|
14092
14150
|
);
|
|
14093
14151
|
}));
|
|
14094
|
-
}), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.refreshText }, "\u21BB Refresh")));
|
|
14152
|
+
}), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles3.refreshText }, "\u21BB", " Refresh")));
|
|
14095
14153
|
}
|
|
14096
14154
|
var styles3 = import_react_native5.StyleSheet.create({
|
|
14097
|
-
filterBar: { flexDirection: "row", gap: 8, marginBottom:
|
|
14155
|
+
filterBar: { flexDirection: "row", gap: 8, marginBottom: 8 },
|
|
14098
14156
|
filterBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
|
|
14099
14157
|
filterBtnActive: { backgroundColor: colors.blue, borderColor: colors.blue },
|
|
14100
14158
|
filterBtnText: { fontSize: 12, color: colors.textSecondary },
|
|
14101
14159
|
filterBtnTextActive: { color: "#fff", fontWeight: "600" },
|
|
14160
|
+
roleSection: { marginBottom: 12 },
|
|
14161
|
+
roleBar: { flexDirection: "row", marginBottom: 6 },
|
|
14162
|
+
roleBtn: { flexDirection: "row", alignItems: "center", gap: 5, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
|
|
14163
|
+
roleBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
|
|
14164
|
+
roleBtnText: { fontSize: 11, color: colors.textMuted },
|
|
14165
|
+
roleBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
|
|
14166
|
+
roleDot: { width: 6, height: 6, borderRadius: 3 },
|
|
14167
|
+
loginHint: { borderRadius: 6, padding: 8, borderWidth: 1 },
|
|
14168
|
+
loginHintText: { fontSize: 11, color: colors.textSecondary },
|
|
14169
|
+
roleBadgeRow: { flexDirection: "row", alignItems: "center", gap: 3 },
|
|
14170
|
+
roleBadgeDot: { width: 5, height: 5, borderRadius: 3 },
|
|
14102
14171
|
folder: { marginBottom: 12 },
|
|
14103
14172
|
folderHeader: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 8, paddingHorizontal: 4 },
|
|
14104
14173
|
folderToggle: { fontSize: 10, color: colors.textMuted, width: 14 },
|
|
@@ -14111,7 +14180,7 @@ var styles3 = import_react_native5.StyleSheet.create({
|
|
|
14111
14180
|
testBadge: { fontSize: 16, marginRight: 10, width: 20 },
|
|
14112
14181
|
testInfo: { flex: 1 },
|
|
14113
14182
|
testTitle: { fontSize: 14, color: colors.textPrimary, marginBottom: 2 },
|
|
14114
|
-
testMetaRow: { flexDirection: "row", alignItems: "center", gap: 6 },
|
|
14183
|
+
testMetaRow: { flexDirection: "row", alignItems: "center", gap: 6, flexWrap: "wrap" },
|
|
14115
14184
|
retestTag: { backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 4, paddingHorizontal: 5, paddingVertical: 1 },
|
|
14116
14185
|
retestTagText: { fontSize: 10, fontWeight: "600", color: "#fbbf24" },
|
|
14117
14186
|
testMeta: { fontSize: 11, color: colors.textDim },
|
|
@@ -14453,11 +14522,14 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14453
14522
|
const [affectedScreen, setAffectedScreen] = (0, import_react10.useState)("");
|
|
14454
14523
|
const [submitting, setSubmitting] = (0, import_react10.useState)(false);
|
|
14455
14524
|
const [error, setError] = (0, import_react10.useState)(null);
|
|
14525
|
+
const submittingRef = (0, import_react10.useRef)(false);
|
|
14456
14526
|
const images = useImageAttachments(uploadImage, 5, "screenshots");
|
|
14457
14527
|
const isRetestFailure = prefill?.type === "test_fail";
|
|
14458
14528
|
const isBugType = reportType === "bug" || reportType === "test_fail";
|
|
14459
14529
|
const handleSubmit = async () => {
|
|
14460
14530
|
if (!client || !description.trim()) return;
|
|
14531
|
+
if (submittingRef.current) return;
|
|
14532
|
+
submittingRef.current = true;
|
|
14461
14533
|
setSubmitting(true);
|
|
14462
14534
|
setError(null);
|
|
14463
14535
|
try {
|
|
@@ -14481,17 +14553,18 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14481
14553
|
console.error("BugBear: Report submission failed", result.error);
|
|
14482
14554
|
setError(result.error || "Failed to submit report. Please try again.");
|
|
14483
14555
|
setSubmitting(false);
|
|
14556
|
+
submittingRef.current = false;
|
|
14484
14557
|
return;
|
|
14485
14558
|
}
|
|
14486
14559
|
if (prefill?.assignmentId) {
|
|
14487
14560
|
await refreshAssignments();
|
|
14488
14561
|
}
|
|
14489
|
-
setSubmitting(false);
|
|
14490
14562
|
nav.replace({ name: "REPORT_SUCCESS" });
|
|
14491
14563
|
} catch (err) {
|
|
14492
14564
|
console.error("BugBear: Report submission error", err);
|
|
14493
14565
|
setError(err instanceof Error ? err.message : "An unexpected error occurred. Please try again.");
|
|
14494
14566
|
setSubmitting(false);
|
|
14567
|
+
submittingRef.current = false;
|
|
14495
14568
|
}
|
|
14496
14569
|
};
|
|
14497
14570
|
return /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, null, isRetestFailure ? /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.retestBanner }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestIcon }, "\u{1F504}"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestTitle }, "Bug Still Present"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.section }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.label }, "Severity"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.severityRow }, [
|
package/dist/index.mjs
CHANGED
|
@@ -11620,6 +11620,7 @@ var HOSTED_BUGBEAR_ANON_KEY = getEnvVar("BUGBEAR_ANON_KEY") || getEnvVar("NEXT_P
|
|
|
11620
11620
|
var BugBearClient = class {
|
|
11621
11621
|
constructor(config) {
|
|
11622
11622
|
this.navigationHistory = [];
|
|
11623
|
+
this.reportSubmitInFlight = false;
|
|
11623
11624
|
this.config = config;
|
|
11624
11625
|
this.supabase = createClient(
|
|
11625
11626
|
config.supabaseUrl || DEFAULT_SUPABASE_URL,
|
|
@@ -11680,6 +11681,10 @@ var BugBearClient = class {
|
|
|
11680
11681
|
* Submit a report
|
|
11681
11682
|
*/
|
|
11682
11683
|
async submitReport(report) {
|
|
11684
|
+
if (this.reportSubmitInFlight) {
|
|
11685
|
+
return { success: false, error: "A report is already being submitted" };
|
|
11686
|
+
}
|
|
11687
|
+
this.reportSubmitInFlight = true;
|
|
11683
11688
|
try {
|
|
11684
11689
|
const validationError = this.validateReport(report);
|
|
11685
11690
|
if (validationError) {
|
|
@@ -11731,6 +11736,8 @@ var BugBearClient = class {
|
|
|
11731
11736
|
} catch (err) {
|
|
11732
11737
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
11733
11738
|
return { success: false, error: message };
|
|
11739
|
+
} finally {
|
|
11740
|
+
this.reportSubmitInFlight = false;
|
|
11734
11741
|
}
|
|
11735
11742
|
}
|
|
11736
11743
|
/**
|
|
@@ -11771,6 +11778,14 @@ var BugBearClient = class {
|
|
|
11771
11778
|
name,
|
|
11772
11779
|
description,
|
|
11773
11780
|
sort_order
|
|
11781
|
+
),
|
|
11782
|
+
role:project_roles(
|
|
11783
|
+
id,
|
|
11784
|
+
name,
|
|
11785
|
+
slug,
|
|
11786
|
+
color,
|
|
11787
|
+
description,
|
|
11788
|
+
login_hint
|
|
11774
11789
|
)
|
|
11775
11790
|
)
|
|
11776
11791
|
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
|
|
@@ -11808,6 +11823,14 @@ var BugBearClient = class {
|
|
|
11808
11823
|
name: item.test_case.group.name,
|
|
11809
11824
|
description: item.test_case.group.description,
|
|
11810
11825
|
sortOrder: item.test_case.group.sort_order
|
|
11826
|
+
} : void 0,
|
|
11827
|
+
role: item.test_case.role ? {
|
|
11828
|
+
id: item.test_case.role.id,
|
|
11829
|
+
name: item.test_case.role.name,
|
|
11830
|
+
slug: item.test_case.role.slug,
|
|
11831
|
+
color: item.test_case.role.color,
|
|
11832
|
+
description: item.test_case.role.description,
|
|
11833
|
+
loginHint: item.test_case.role.login_hint
|
|
11811
11834
|
} : void 0
|
|
11812
11835
|
}
|
|
11813
11836
|
}));
|
|
@@ -12223,8 +12246,8 @@ var BugBearClient = class {
|
|
|
12223
12246
|
return "Maximum 10 screenshots allowed";
|
|
12224
12247
|
}
|
|
12225
12248
|
for (const url of report.screenshots) {
|
|
12226
|
-
if (typeof url !== "string" || url.length > 2e3) {
|
|
12227
|
-
return "Invalid screenshot URL";
|
|
12249
|
+
if (typeof url !== "string" || url.length > 2e3 || !/^https?:\/\//i.test(url)) {
|
|
12250
|
+
return "Invalid screenshot URL (must be an HTTP/HTTPS URL)";
|
|
12228
12251
|
}
|
|
12229
12252
|
}
|
|
12230
12253
|
}
|
|
@@ -12613,7 +12636,10 @@ var BugBearClient = class {
|
|
|
12613
12636
|
content
|
|
12614
12637
|
};
|
|
12615
12638
|
if (attachments && attachments.length > 0) {
|
|
12616
|
-
|
|
12639
|
+
const safeAttachments = attachments.filter((a) => /^https?:\/\//i.test(a.url));
|
|
12640
|
+
if (safeAttachments.length > 0) {
|
|
12641
|
+
insertData.attachments = safeAttachments;
|
|
12642
|
+
}
|
|
12617
12643
|
}
|
|
12618
12644
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
12619
12645
|
if (error) {
|
|
@@ -13213,14 +13239,14 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13213
13239
|
}
|
|
13214
13240
|
|
|
13215
13241
|
// src/BugBearButton.tsx
|
|
13216
|
-
import React14, { useState as useState10, useRef as
|
|
13242
|
+
import React14, { useState as useState10, useRef as useRef3 } from "react";
|
|
13217
13243
|
import {
|
|
13218
13244
|
View as View13,
|
|
13219
13245
|
Text as Text13,
|
|
13220
13246
|
Image as Image3,
|
|
13221
13247
|
TouchableOpacity as TouchableOpacity12,
|
|
13222
13248
|
Modal as Modal2,
|
|
13223
|
-
ScrollView as
|
|
13249
|
+
ScrollView as ScrollView3,
|
|
13224
13250
|
StyleSheet as StyleSheet14,
|
|
13225
13251
|
Dimensions as Dimensions2,
|
|
13226
13252
|
KeyboardAvoidingView,
|
|
@@ -13999,14 +14025,23 @@ var styles2 = StyleSheet3.create({
|
|
|
13999
14025
|
|
|
14000
14026
|
// src/widget/screens/TestListScreen.tsx
|
|
14001
14027
|
import React4, { useState as useState3, useMemo as useMemo2, useCallback as useCallback3, useEffect as useEffect4 } from "react";
|
|
14002
|
-
import { View as View3, Text as Text3, TouchableOpacity as TouchableOpacity3, StyleSheet as StyleSheet4 } from "react-native";
|
|
14028
|
+
import { View as View3, Text as Text3, TouchableOpacity as TouchableOpacity3, StyleSheet as StyleSheet4, ScrollView } from "react-native";
|
|
14003
14029
|
function TestListScreen({ nav }) {
|
|
14004
14030
|
const { assignments, currentAssignment, refreshAssignments } = useBugBear();
|
|
14005
14031
|
const [filter, setFilter] = useState3("all");
|
|
14032
|
+
const [roleFilter, setRoleFilter] = useState3(null);
|
|
14006
14033
|
const [collapsedFolders, setCollapsedFolders] = useState3(/* @__PURE__ */ new Set());
|
|
14007
14034
|
useEffect4(() => {
|
|
14008
14035
|
refreshAssignments();
|
|
14009
14036
|
}, []);
|
|
14037
|
+
const availableRoles = useMemo2(() => {
|
|
14038
|
+
const roleMap = /* @__PURE__ */ new Map();
|
|
14039
|
+
for (const a of assignments) {
|
|
14040
|
+
if (a.testCase.role) roleMap.set(a.testCase.role.id, a.testCase.role);
|
|
14041
|
+
}
|
|
14042
|
+
return Array.from(roleMap.values());
|
|
14043
|
+
}, [assignments]);
|
|
14044
|
+
const selectedRole = availableRoles.find((r) => r.id === roleFilter);
|
|
14010
14045
|
const groupedAssignments = useMemo2(() => {
|
|
14011
14046
|
const groups = /* @__PURE__ */ new Map();
|
|
14012
14047
|
for (const assignment of assignments) {
|
|
@@ -14050,16 +14085,39 @@ function TestListScreen({ nav }) {
|
|
|
14050
14085
|
});
|
|
14051
14086
|
}, []);
|
|
14052
14087
|
const filterAssignment = (a) => {
|
|
14088
|
+
if (roleFilter && a.testCase.role?.id !== roleFilter) return false;
|
|
14053
14089
|
if (filter === "pending") return a.status === "pending" || a.status === "in_progress";
|
|
14054
14090
|
if (filter === "completed") return a.status === "passed" || a.status === "failed";
|
|
14055
14091
|
return true;
|
|
14056
14092
|
};
|
|
14057
|
-
return /* @__PURE__ */ React4.createElement(View3, null, /* @__PURE__ */ React4.createElement(View3, { style: styles3.filterBar }, ["all", "pending", "completed"].map((f) => /* @__PURE__ */ React4.createElement(TouchableOpacity3, { key: f, style: [styles3.filterBtn, filter === f && styles3.filterBtnActive], onPress: () => setFilter(f) }, /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.filterBtnText, filter === f && styles3.filterBtnTextActive] }, f === "all" ? `All (${assignments.length})` : f === "pending" ? `To Do (${assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length})` : `Done (${assignments.filter((a) => a.status === "passed" || a.status === "failed").length})`)))),
|
|
14093
|
+
return /* @__PURE__ */ React4.createElement(View3, null, /* @__PURE__ */ React4.createElement(View3, { style: styles3.filterBar }, ["all", "pending", "completed"].map((f) => /* @__PURE__ */ React4.createElement(TouchableOpacity3, { key: f, style: [styles3.filterBtn, filter === f && styles3.filterBtnActive], onPress: () => setFilter(f) }, /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.filterBtnText, filter === f && styles3.filterBtnTextActive] }, f === "all" ? `All (${assignments.length})` : f === "pending" ? `To Do (${assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length})` : `Done (${assignments.filter((a) => a.status === "passed" || a.status === "failed").length})`)))), availableRoles.length >= 2 && /* @__PURE__ */ React4.createElement(View3, { style: styles3.roleSection }, /* @__PURE__ */ React4.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.roleBar }, /* @__PURE__ */ React4.createElement(
|
|
14094
|
+
TouchableOpacity3,
|
|
14095
|
+
{
|
|
14096
|
+
style: [styles3.roleBtn, !roleFilter && styles3.roleBtnActive],
|
|
14097
|
+
onPress: () => setRoleFilter(null)
|
|
14098
|
+
},
|
|
14099
|
+
/* @__PURE__ */ React4.createElement(Text3, { style: [styles3.roleBtnText, !roleFilter && styles3.roleBtnTextActive] }, "All Roles")
|
|
14100
|
+
), availableRoles.map((role) => {
|
|
14101
|
+
const isActive = roleFilter === role.id;
|
|
14102
|
+
return /* @__PURE__ */ React4.createElement(
|
|
14103
|
+
TouchableOpacity3,
|
|
14104
|
+
{
|
|
14105
|
+
key: role.id,
|
|
14106
|
+
style: [
|
|
14107
|
+
styles3.roleBtn,
|
|
14108
|
+
isActive && { backgroundColor: role.color + "20", borderColor: role.color + "60", borderWidth: 1 }
|
|
14109
|
+
],
|
|
14110
|
+
onPress: () => setRoleFilter(isActive ? null : role.id)
|
|
14111
|
+
},
|
|
14112
|
+
/* @__PURE__ */ React4.createElement(View3, { style: [styles3.roleDot, { backgroundColor: role.color }] }),
|
|
14113
|
+
/* @__PURE__ */ React4.createElement(Text3, { style: [styles3.roleBtnText, isActive && { color: role.color, fontWeight: "600" }] }, role.name)
|
|
14114
|
+
);
|
|
14115
|
+
})), selectedRole?.loginHint && /* @__PURE__ */ React4.createElement(View3, { style: [styles3.loginHint, { backgroundColor: selectedRole.color + "10", borderColor: selectedRole.color + "30" }] }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.loginHintText }, "Log in as: ", selectedRole.loginHint))), groupedAssignments.map((folder) => {
|
|
14058
14116
|
const folderId = folder.group?.id || "ungrouped";
|
|
14059
14117
|
const isCollapsed = collapsedFolders.has(folderId);
|
|
14060
14118
|
const filtered = folder.assignments.filter(filterAssignment);
|
|
14061
14119
|
if (filtered.length === 0 && filter !== "all") return null;
|
|
14062
|
-
return /* @__PURE__ */ React4.createElement(View3, { key: folderId, style: styles3.folder }, /* @__PURE__ */ React4.createElement(TouchableOpacity3, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderName }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ React4.createElement(View3, { style: styles3.folderProgress }, /* @__PURE__ */ React4.createElement(View3, { style: [styles3.folderProgressFill, { width: `${Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100)}%` }] })), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
|
|
14120
|
+
return /* @__PURE__ */ React4.createElement(View3, { key: folderId, style: styles3.folder }, /* @__PURE__ */ React4.createElement(TouchableOpacity3, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderName, numberOfLines: 1 }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ React4.createElement(View3, { style: styles3.folderProgress }, /* @__PURE__ */ React4.createElement(View3, { style: [styles3.folderProgressFill, { width: `${folder.stats.total > 0 ? Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100) : 0}%` }] })), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
|
|
14063
14121
|
const badge = getStatusBadge(assignment.status);
|
|
14064
14122
|
const isCurrent = currentAssignment?.id === assignment.id;
|
|
14065
14123
|
return /* @__PURE__ */ React4.createElement(
|
|
@@ -14070,17 +14128,28 @@ function TestListScreen({ nav }) {
|
|
|
14070
14128
|
onPress: () => nav.push({ name: "TEST_DETAIL", testId: assignment.id })
|
|
14071
14129
|
},
|
|
14072
14130
|
/* @__PURE__ */ React4.createElement(Text3, { style: styles3.testBadge }, badge.icon),
|
|
14073
|
-
/* @__PURE__ */ React4.createElement(View3, { style: styles3.testInfo }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ React4.createElement(View3, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ React4.createElement(View3, { style: styles3.retestTag }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testMeta }, assignment.testCase.
|
|
14131
|
+
/* @__PURE__ */ React4.createElement(View3, { style: styles3.testInfo }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ React4.createElement(View3, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ React4.createElement(View3, { style: styles3.retestTag }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ React4.createElement(View3, { style: styles3.roleBadgeRow }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.testMeta }, " \xB7 "), /* @__PURE__ */ React4.createElement(View3, { style: [styles3.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ React4.createElement(Text3, { style: [styles3.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name))))
|
|
14074
14132
|
);
|
|
14075
14133
|
}));
|
|
14076
|
-
}), /* @__PURE__ */ React4.createElement(TouchableOpacity3, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.refreshText }, "\u21BB Refresh")));
|
|
14134
|
+
}), /* @__PURE__ */ React4.createElement(TouchableOpacity3, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ React4.createElement(Text3, { style: styles3.refreshText }, "\u21BB", " Refresh")));
|
|
14077
14135
|
}
|
|
14078
14136
|
var styles3 = StyleSheet4.create({
|
|
14079
|
-
filterBar: { flexDirection: "row", gap: 8, marginBottom:
|
|
14137
|
+
filterBar: { flexDirection: "row", gap: 8, marginBottom: 8 },
|
|
14080
14138
|
filterBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
|
|
14081
14139
|
filterBtnActive: { backgroundColor: colors.blue, borderColor: colors.blue },
|
|
14082
14140
|
filterBtnText: { fontSize: 12, color: colors.textSecondary },
|
|
14083
14141
|
filterBtnTextActive: { color: "#fff", fontWeight: "600" },
|
|
14142
|
+
roleSection: { marginBottom: 12 },
|
|
14143
|
+
roleBar: { flexDirection: "row", marginBottom: 6 },
|
|
14144
|
+
roleBtn: { flexDirection: "row", alignItems: "center", gap: 5, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
|
|
14145
|
+
roleBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
|
|
14146
|
+
roleBtnText: { fontSize: 11, color: colors.textMuted },
|
|
14147
|
+
roleBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
|
|
14148
|
+
roleDot: { width: 6, height: 6, borderRadius: 3 },
|
|
14149
|
+
loginHint: { borderRadius: 6, padding: 8, borderWidth: 1 },
|
|
14150
|
+
loginHintText: { fontSize: 11, color: colors.textSecondary },
|
|
14151
|
+
roleBadgeRow: { flexDirection: "row", alignItems: "center", gap: 3 },
|
|
14152
|
+
roleBadgeDot: { width: 5, height: 5, borderRadius: 3 },
|
|
14084
14153
|
folder: { marginBottom: 12 },
|
|
14085
14154
|
folderHeader: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 8, paddingHorizontal: 4 },
|
|
14086
14155
|
folderToggle: { fontSize: 10, color: colors.textMuted, width: 14 },
|
|
@@ -14093,7 +14162,7 @@ var styles3 = StyleSheet4.create({
|
|
|
14093
14162
|
testBadge: { fontSize: 16, marginRight: 10, width: 20 },
|
|
14094
14163
|
testInfo: { flex: 1 },
|
|
14095
14164
|
testTitle: { fontSize: 14, color: colors.textPrimary, marginBottom: 2 },
|
|
14096
|
-
testMetaRow: { flexDirection: "row", alignItems: "center", gap: 6 },
|
|
14165
|
+
testMetaRow: { flexDirection: "row", alignItems: "center", gap: 6, flexWrap: "wrap" },
|
|
14097
14166
|
retestTag: { backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 4, paddingHorizontal: 5, paddingVertical: 1 },
|
|
14098
14167
|
retestTagText: { fontSize: 10, fontWeight: "600", color: "#fbbf24" },
|
|
14099
14168
|
testMeta: { fontSize: 11, color: colors.textDim },
|
|
@@ -14188,10 +14257,10 @@ import { View as View5, Text as Text5, TouchableOpacity as TouchableOpacity5, St
|
|
|
14188
14257
|
|
|
14189
14258
|
// src/widget/ImagePreviewStrip.tsx
|
|
14190
14259
|
import React5 from "react";
|
|
14191
|
-
import { View as View4, Text as Text4, TouchableOpacity as TouchableOpacity4, ScrollView, Image, ActivityIndicator, StyleSheet as StyleSheet5 } from "react-native";
|
|
14260
|
+
import { View as View4, Text as Text4, TouchableOpacity as TouchableOpacity4, ScrollView as ScrollView2, Image, ActivityIndicator, StyleSheet as StyleSheet5 } from "react-native";
|
|
14192
14261
|
function ImagePreviewStrip({ images, onRemove }) {
|
|
14193
14262
|
if (images.length === 0) return null;
|
|
14194
|
-
return /* @__PURE__ */ React5.createElement(
|
|
14263
|
+
return /* @__PURE__ */ React5.createElement(ScrollView2, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles4.strip }, images.map((img) => /* @__PURE__ */ React5.createElement(View4, { key: img.id, style: styles4.thumbContainer }, /* @__PURE__ */ React5.createElement(Image, { source: { uri: img.localUri }, style: styles4.thumb }), img.status === "uploading" && /* @__PURE__ */ React5.createElement(View4, { style: styles4.thumbOverlay }, /* @__PURE__ */ React5.createElement(ActivityIndicator, { size: "small", color: "#fff" })), img.status === "error" && /* @__PURE__ */ React5.createElement(View4, { style: [styles4.thumbOverlay, styles4.thumbOverlayError] }, /* @__PURE__ */ React5.createElement(Text4, { style: styles4.thumbErrorText }, "!")), /* @__PURE__ */ React5.createElement(TouchableOpacity4, { style: styles4.thumbRemove, onPress: () => onRemove(img.id) }, /* @__PURE__ */ React5.createElement(Text4, { style: styles4.thumbRemoveText }, "\u2715")))));
|
|
14195
14264
|
}
|
|
14196
14265
|
var styles4 = StyleSheet5.create({
|
|
14197
14266
|
strip: {
|
|
@@ -14425,7 +14494,7 @@ var styles6 = StyleSheet7.create({
|
|
|
14425
14494
|
});
|
|
14426
14495
|
|
|
14427
14496
|
// src/widget/screens/ReportScreen.tsx
|
|
14428
|
-
import React8, { useState as useState6 } from "react";
|
|
14497
|
+
import React8, { useState as useState6, useRef as useRef2 } from "react";
|
|
14429
14498
|
import { View as View7, Text as Text7, TouchableOpacity as TouchableOpacity7, TextInput as TextInput3, StyleSheet as StyleSheet8 } from "react-native";
|
|
14430
14499
|
function ReportScreen({ nav, prefill }) {
|
|
14431
14500
|
const { client, getDeviceInfo, uploadImage, refreshAssignments } = useBugBear();
|
|
@@ -14435,11 +14504,14 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14435
14504
|
const [affectedScreen, setAffectedScreen] = useState6("");
|
|
14436
14505
|
const [submitting, setSubmitting] = useState6(false);
|
|
14437
14506
|
const [error, setError] = useState6(null);
|
|
14507
|
+
const submittingRef = useRef2(false);
|
|
14438
14508
|
const images = useImageAttachments(uploadImage, 5, "screenshots");
|
|
14439
14509
|
const isRetestFailure = prefill?.type === "test_fail";
|
|
14440
14510
|
const isBugType = reportType === "bug" || reportType === "test_fail";
|
|
14441
14511
|
const handleSubmit = async () => {
|
|
14442
14512
|
if (!client || !description.trim()) return;
|
|
14513
|
+
if (submittingRef.current) return;
|
|
14514
|
+
submittingRef.current = true;
|
|
14443
14515
|
setSubmitting(true);
|
|
14444
14516
|
setError(null);
|
|
14445
14517
|
try {
|
|
@@ -14463,17 +14535,18 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14463
14535
|
console.error("BugBear: Report submission failed", result.error);
|
|
14464
14536
|
setError(result.error || "Failed to submit report. Please try again.");
|
|
14465
14537
|
setSubmitting(false);
|
|
14538
|
+
submittingRef.current = false;
|
|
14466
14539
|
return;
|
|
14467
14540
|
}
|
|
14468
14541
|
if (prefill?.assignmentId) {
|
|
14469
14542
|
await refreshAssignments();
|
|
14470
14543
|
}
|
|
14471
|
-
setSubmitting(false);
|
|
14472
14544
|
nav.replace({ name: "REPORT_SUCCESS" });
|
|
14473
14545
|
} catch (err) {
|
|
14474
14546
|
console.error("BugBear: Report submission error", err);
|
|
14475
14547
|
setError(err instanceof Error ? err.message : "An unexpected error occurred. Please try again.");
|
|
14476
14548
|
setSubmitting(false);
|
|
14549
|
+
submittingRef.current = false;
|
|
14477
14550
|
}
|
|
14478
14551
|
};
|
|
14479
14552
|
return /* @__PURE__ */ React8.createElement(View7, null, isRetestFailure ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(View7, { style: styles7.retestBanner }, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestIcon }, "\u{1F504}"), /* @__PURE__ */ React8.createElement(View7, null, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestTitle }, "Bug Still Present"), /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ React8.createElement(View7, { style: styles7.section }, /* @__PURE__ */ React8.createElement(Text7, { style: shared.label }, "Severity"), /* @__PURE__ */ React8.createElement(View7, { style: styles7.severityRow }, [
|
|
@@ -15003,9 +15076,9 @@ function BugBearButton({
|
|
|
15003
15076
|
return { x, y };
|
|
15004
15077
|
};
|
|
15005
15078
|
const initialPos = getInitialPosition();
|
|
15006
|
-
const pan =
|
|
15007
|
-
const isDragging =
|
|
15008
|
-
const panResponder =
|
|
15079
|
+
const pan = useRef3(new Animated.ValueXY(initialPos)).current;
|
|
15080
|
+
const isDragging = useRef3(false);
|
|
15081
|
+
const panResponder = useRef3(
|
|
15009
15082
|
PanResponder.create({
|
|
15010
15083
|
onStartShouldSetPanResponder: () => draggable,
|
|
15011
15084
|
onMoveShouldSetPanResponder: (_, gs) => draggable && (Math.abs(gs.dx) > 5 || Math.abs(gs.dy) > 5),
|
|
@@ -15157,7 +15230,7 @@ function BugBearButton({
|
|
|
15157
15230
|
style: styles13.modalOverlay
|
|
15158
15231
|
},
|
|
15159
15232
|
/* @__PURE__ */ React14.createElement(View13, { style: styles13.modalContainer }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.header }, /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerLeft }, canGoBack ? /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerNavRow }, /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => nav.pop(), style: styles13.backButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.backText }, "\u2190 Back")), /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => nav.reset(), style: styles13.homeButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ React14.createElement(View13, { style: styles13.headerTitleRow }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React14.createElement(Text13, { style: styles13.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React14.createElement(TouchableOpacity12, { onPress: handleClose, style: styles13.closeButton }, /* @__PURE__ */ React14.createElement(Text13, { style: styles13.closeText }, "\u2715"))), /* @__PURE__ */ React14.createElement(
|
|
15160
|
-
|
|
15233
|
+
ScrollView3,
|
|
15161
15234
|
{
|
|
15162
15235
|
style: styles13.content,
|
|
15163
15236
|
contentContainerStyle: styles13.contentContainer,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbearai/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "BugBear React Native components for mobile apps",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@bbearai/core": "^0.
|
|
52
|
+
"@bbearai/core": "^0.4.0",
|
|
53
53
|
"@eslint/js": "^9.39.2",
|
|
54
54
|
"@types/react": "^18.2.0",
|
|
55
55
|
"eslint": "^9.39.2",
|