@bbearai/react-native 0.8.3 → 0.8.4
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 +332 -7
- package/dist/index.mjs +389 -64
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -12235,6 +12235,9 @@ var BugBearClient = class {
|
|
|
12235
12235
|
this.monitor = null;
|
|
12236
12236
|
this.initialized = false;
|
|
12237
12237
|
this.initError = null;
|
|
12238
|
+
this._presenceSessionId = null;
|
|
12239
|
+
this._presenceInterval = null;
|
|
12240
|
+
this._presencePaused = false;
|
|
12238
12241
|
this.config = config;
|
|
12239
12242
|
if (config.apiKey) {
|
|
12240
12243
|
this.pendingInit = this.resolveFromApiKey(config.apiKey);
|
|
@@ -13880,6 +13883,7 @@ var BugBearClient = class {
|
|
|
13880
13883
|
lastMessageAt: row.last_message_at,
|
|
13881
13884
|
createdAt: row.created_at,
|
|
13882
13885
|
unreadCount: Number(row.unread_count) || 0,
|
|
13886
|
+
reporterName: row.reporter_name || void 0,
|
|
13883
13887
|
lastMessage: row.last_message_preview ? {
|
|
13884
13888
|
id: "",
|
|
13885
13889
|
threadId: row.thread_id,
|
|
@@ -14319,6 +14323,93 @@ var BugBearClient = class {
|
|
|
14319
14323
|
updatedAt: data.updated_at
|
|
14320
14324
|
};
|
|
14321
14325
|
}
|
|
14326
|
+
// ─── Passive Presence Tracking ──────────────────────────────
|
|
14327
|
+
/** Current presence session ID (null if not tracking). */
|
|
14328
|
+
get presenceSessionId() {
|
|
14329
|
+
return this._presenceSessionId;
|
|
14330
|
+
}
|
|
14331
|
+
/**
|
|
14332
|
+
* Start passive presence tracking for this tester.
|
|
14333
|
+
* Idempotent — reuses an existing active session if one exists.
|
|
14334
|
+
*/
|
|
14335
|
+
async startPresence(platform) {
|
|
14336
|
+
try {
|
|
14337
|
+
await this.ensureReady();
|
|
14338
|
+
const testerInfo = await this.getTesterInfo();
|
|
14339
|
+
if (!testerInfo) return null;
|
|
14340
|
+
const { data, error } = await this.supabase.rpc("upsert_tester_presence", {
|
|
14341
|
+
p_project_id: this.config.projectId,
|
|
14342
|
+
p_tester_id: testerInfo.id,
|
|
14343
|
+
p_platform: platform
|
|
14344
|
+
});
|
|
14345
|
+
if (error) {
|
|
14346
|
+
console.error("BugBear: Failed to start presence", formatPgError(error));
|
|
14347
|
+
return null;
|
|
14348
|
+
}
|
|
14349
|
+
this._presenceSessionId = data;
|
|
14350
|
+
this._presencePaused = false;
|
|
14351
|
+
this.startPresenceHeartbeat();
|
|
14352
|
+
return data;
|
|
14353
|
+
} catch (err) {
|
|
14354
|
+
console.error("BugBear: Error starting presence", err);
|
|
14355
|
+
return null;
|
|
14356
|
+
}
|
|
14357
|
+
}
|
|
14358
|
+
/** Gracefully end the current presence session. */
|
|
14359
|
+
async endPresence() {
|
|
14360
|
+
this.stopPresenceHeartbeat();
|
|
14361
|
+
if (!this._presenceSessionId) return;
|
|
14362
|
+
try {
|
|
14363
|
+
await this.supabase.rpc("end_tester_presence", {
|
|
14364
|
+
p_session_id: this._presenceSessionId
|
|
14365
|
+
});
|
|
14366
|
+
} catch {
|
|
14367
|
+
}
|
|
14368
|
+
this._presenceSessionId = null;
|
|
14369
|
+
}
|
|
14370
|
+
/** Pause heartbeat (tab hidden / app backgrounded). Sends one final beat. */
|
|
14371
|
+
pausePresence() {
|
|
14372
|
+
this._presencePaused = true;
|
|
14373
|
+
this.heartbeatPresence();
|
|
14374
|
+
}
|
|
14375
|
+
/** Resume heartbeat after pause. Restarts if session was cleaned up. */
|
|
14376
|
+
async resumePresence() {
|
|
14377
|
+
if (!this._presenceSessionId) return;
|
|
14378
|
+
this._presencePaused = false;
|
|
14379
|
+
try {
|
|
14380
|
+
const { data } = await this.supabase.rpc("heartbeat_tester_presence", {
|
|
14381
|
+
p_session_id: this._presenceSessionId
|
|
14382
|
+
});
|
|
14383
|
+
if (!data) {
|
|
14384
|
+
this._presenceSessionId = null;
|
|
14385
|
+
}
|
|
14386
|
+
} catch {
|
|
14387
|
+
this._presenceSessionId = null;
|
|
14388
|
+
}
|
|
14389
|
+
}
|
|
14390
|
+
async heartbeatPresence() {
|
|
14391
|
+
if (!this._presenceSessionId || this._presencePaused) return;
|
|
14392
|
+
try {
|
|
14393
|
+
const { data, error } = await this.supabase.rpc("heartbeat_tester_presence", {
|
|
14394
|
+
p_session_id: this._presenceSessionId
|
|
14395
|
+
});
|
|
14396
|
+
if (error || data === false) {
|
|
14397
|
+
this.stopPresenceHeartbeat();
|
|
14398
|
+
this._presenceSessionId = null;
|
|
14399
|
+
}
|
|
14400
|
+
} catch {
|
|
14401
|
+
}
|
|
14402
|
+
}
|
|
14403
|
+
startPresenceHeartbeat() {
|
|
14404
|
+
this.stopPresenceHeartbeat();
|
|
14405
|
+
this._presenceInterval = setInterval(() => this.heartbeatPresence(), 6e4);
|
|
14406
|
+
}
|
|
14407
|
+
stopPresenceHeartbeat() {
|
|
14408
|
+
if (this._presenceInterval) {
|
|
14409
|
+
clearInterval(this._presenceInterval);
|
|
14410
|
+
this._presenceInterval = null;
|
|
14411
|
+
}
|
|
14412
|
+
}
|
|
14322
14413
|
};
|
|
14323
14414
|
function createBugBear(config) {
|
|
14324
14415
|
return new BugBearClient(config);
|
|
@@ -14453,6 +14544,9 @@ function setActiveColors(palette) {
|
|
|
14453
14544
|
colors = palette;
|
|
14454
14545
|
shared = createSharedStyles();
|
|
14455
14546
|
}
|
|
14547
|
+
function withAlpha(hex, alpha) {
|
|
14548
|
+
return hex + Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
14549
|
+
}
|
|
14456
14550
|
function createSharedStyles() {
|
|
14457
14551
|
return import_react_native.StyleSheet.create({
|
|
14458
14552
|
card: {
|
|
@@ -14955,6 +15049,28 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14955
15049
|
}
|
|
14956
15050
|
return () => subscription.remove();
|
|
14957
15051
|
}, [client]);
|
|
15052
|
+
(0, import_react.useEffect)(() => {
|
|
15053
|
+
if (!client || !isTester) return;
|
|
15054
|
+
let mounted = true;
|
|
15055
|
+
const platform = import_react_native2.Platform.OS === "ios" ? "ios" : "android";
|
|
15056
|
+
client.startPresence(platform);
|
|
15057
|
+
const subscription = import_react_native2.AppState.addEventListener("change", (nextState) => {
|
|
15058
|
+
if (nextState === "active") {
|
|
15059
|
+
client.resumePresence().then(() => {
|
|
15060
|
+
if (mounted && !client.presenceSessionId) {
|
|
15061
|
+
client.startPresence(platform);
|
|
15062
|
+
}
|
|
15063
|
+
});
|
|
15064
|
+
} else if (nextState === "background" || nextState === "inactive") {
|
|
15065
|
+
client.pausePresence();
|
|
15066
|
+
}
|
|
15067
|
+
});
|
|
15068
|
+
return () => {
|
|
15069
|
+
mounted = false;
|
|
15070
|
+
subscription.remove();
|
|
15071
|
+
client.endPresence();
|
|
15072
|
+
};
|
|
15073
|
+
}, [client, isTester]);
|
|
14958
15074
|
(0, import_react.useEffect)(() => {
|
|
14959
15075
|
if (!client || !isTester) return;
|
|
14960
15076
|
if (widgetMode === "qa" && !isQAEnabled) return;
|
|
@@ -16952,6 +17068,8 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16952
17068
|
const [severity, setSeverity] = (0, import_react12.useState)("medium");
|
|
16953
17069
|
const [category, setCategory] = (0, import_react12.useState)(null);
|
|
16954
17070
|
const [description, setDescription] = (0, import_react12.useState)("");
|
|
17071
|
+
const [myIssues, setMyIssues] = (0, import_react12.useState)([]);
|
|
17072
|
+
const [similarReports, setSimilarReports] = (0, import_react12.useState)([]);
|
|
16955
17073
|
const [affectedScreen, setAffectedScreen] = (0, import_react12.useState)("");
|
|
16956
17074
|
const [submitting, setSubmitting] = (0, import_react12.useState)(false);
|
|
16957
17075
|
const [error, setError] = (0, import_react12.useState)(null);
|
|
@@ -16967,6 +17085,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16967
17085
|
}, [autoCaptureUri, onAutoCaptureConsumed]);
|
|
16968
17086
|
const isRetestFailure = prefill?.type === "test_fail";
|
|
16969
17087
|
const isBugType = reportType === "bug" || reportType === "test_fail";
|
|
17088
|
+
(0, import_react12.useEffect)(() => {
|
|
17089
|
+
if (!client?.getIssues) return;
|
|
17090
|
+
let cancelled = false;
|
|
17091
|
+
const load = async () => {
|
|
17092
|
+
try {
|
|
17093
|
+
const [open, done] = await Promise.all([
|
|
17094
|
+
client.getIssues("open"),
|
|
17095
|
+
client.getIssues("done")
|
|
17096
|
+
]);
|
|
17097
|
+
if (!cancelled) setMyIssues([...open, ...done]);
|
|
17098
|
+
} catch {
|
|
17099
|
+
}
|
|
17100
|
+
};
|
|
17101
|
+
load();
|
|
17102
|
+
return () => {
|
|
17103
|
+
cancelled = true;
|
|
17104
|
+
};
|
|
17105
|
+
}, [client]);
|
|
17106
|
+
(0, import_react12.useEffect)(() => {
|
|
17107
|
+
if (description.length < 10 || myIssues.length === 0) {
|
|
17108
|
+
setSimilarReports([]);
|
|
17109
|
+
return;
|
|
17110
|
+
}
|
|
17111
|
+
const words = description.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
17112
|
+
if (words.length === 0) {
|
|
17113
|
+
setSimilarReports([]);
|
|
17114
|
+
return;
|
|
17115
|
+
}
|
|
17116
|
+
const scored = myIssues.map((issue) => {
|
|
17117
|
+
const text = `${issue.title || ""} ${issue.description || ""}`.toLowerCase();
|
|
17118
|
+
const matches = words.filter((w) => text.includes(w)).length;
|
|
17119
|
+
return { issue, score: matches / words.length };
|
|
17120
|
+
}).filter((s2) => s2.score >= 0.3).sort((a, b) => b.score - a.score).slice(0, 3);
|
|
17121
|
+
setSimilarReports(scored.map((s2) => s2.issue));
|
|
17122
|
+
}, [description, myIssues]);
|
|
16970
17123
|
(0, import_react12.useEffect)(() => {
|
|
16971
17124
|
if (reportType === "feedback" || reportType === "suggestion") {
|
|
16972
17125
|
setCategory("other");
|
|
@@ -17041,7 +17194,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
17041
17194
|
numberOfLines: 4,
|
|
17042
17195
|
textAlignVertical: "top"
|
|
17043
17196
|
}
|
|
17044
|
-
)
|
|
17197
|
+
), similarReports.length > 0 && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: {
|
|
17198
|
+
marginTop: 8,
|
|
17199
|
+
padding: 12,
|
|
17200
|
+
borderRadius: 8,
|
|
17201
|
+
backgroundColor: withAlpha(colors.orange, 0.1),
|
|
17202
|
+
borderWidth: 1,
|
|
17203
|
+
borderColor: withAlpha(colors.orange, 0.3)
|
|
17204
|
+
} }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: { fontSize: 12, fontWeight: "600", color: colors.orange, marginBottom: 8 } }, "Similar reports you've already filed:"), similarReports.map((issue) => /* @__PURE__ */ import_react12.default.createElement(
|
|
17205
|
+
import_react_native11.TouchableOpacity,
|
|
17206
|
+
{
|
|
17207
|
+
key: issue.id,
|
|
17208
|
+
onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
|
|
17209
|
+
style: {
|
|
17210
|
+
flexDirection: "row",
|
|
17211
|
+
alignItems: "center",
|
|
17212
|
+
gap: 8,
|
|
17213
|
+
paddingVertical: 6
|
|
17214
|
+
}
|
|
17215
|
+
},
|
|
17216
|
+
/* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: {
|
|
17217
|
+
width: 8,
|
|
17218
|
+
height: 8,
|
|
17219
|
+
borderRadius: 4,
|
|
17220
|
+
backgroundColor: issue.severity === "critical" ? colors.red : issue.severity === "high" ? colors.orange : colors.yellow
|
|
17221
|
+
} }),
|
|
17222
|
+
/* @__PURE__ */ import_react12.default.createElement(
|
|
17223
|
+
import_react_native11.Text,
|
|
17224
|
+
{
|
|
17225
|
+
numberOfLines: 1,
|
|
17226
|
+
style: { fontSize: 12, color: colors.textPrimary, flex: 1 }
|
|
17227
|
+
},
|
|
17228
|
+
issue.title
|
|
17229
|
+
),
|
|
17230
|
+
/* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: { fontSize: 11, color: colors.textMuted } }, issue.status)
|
|
17231
|
+
)))), /* @__PURE__ */ import_react12.default.createElement(
|
|
17045
17232
|
ImagePickerButtons,
|
|
17046
17233
|
{
|
|
17047
17234
|
images: images.images,
|
|
@@ -17097,7 +17284,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
17097
17284
|
numberOfLines: 4,
|
|
17098
17285
|
textAlignVertical: "top"
|
|
17099
17286
|
}
|
|
17100
|
-
)
|
|
17287
|
+
), similarReports.length > 0 && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: {
|
|
17288
|
+
marginTop: 8,
|
|
17289
|
+
padding: 12,
|
|
17290
|
+
borderRadius: 8,
|
|
17291
|
+
backgroundColor: withAlpha(colors.orange, 0.1),
|
|
17292
|
+
borderWidth: 1,
|
|
17293
|
+
borderColor: withAlpha(colors.orange, 0.3)
|
|
17294
|
+
} }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: { fontSize: 12, fontWeight: "600", color: colors.orange, marginBottom: 8 } }, "Similar reports you've already filed:"), similarReports.map((issue) => /* @__PURE__ */ import_react12.default.createElement(
|
|
17295
|
+
import_react_native11.TouchableOpacity,
|
|
17296
|
+
{
|
|
17297
|
+
key: issue.id,
|
|
17298
|
+
onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
|
|
17299
|
+
style: {
|
|
17300
|
+
flexDirection: "row",
|
|
17301
|
+
alignItems: "center",
|
|
17302
|
+
gap: 8,
|
|
17303
|
+
paddingVertical: 6
|
|
17304
|
+
}
|
|
17305
|
+
},
|
|
17306
|
+
/* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: {
|
|
17307
|
+
width: 8,
|
|
17308
|
+
height: 8,
|
|
17309
|
+
borderRadius: 4,
|
|
17310
|
+
backgroundColor: issue.severity === "critical" ? colors.red : issue.severity === "high" ? colors.orange : colors.yellow
|
|
17311
|
+
} }),
|
|
17312
|
+
/* @__PURE__ */ import_react12.default.createElement(
|
|
17313
|
+
import_react_native11.Text,
|
|
17314
|
+
{
|
|
17315
|
+
numberOfLines: 1,
|
|
17316
|
+
style: { fontSize: 12, color: colors.textPrimary, flex: 1 }
|
|
17317
|
+
},
|
|
17318
|
+
issue.title
|
|
17319
|
+
),
|
|
17320
|
+
/* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: { fontSize: 11, color: colors.textMuted } }, issue.status)
|
|
17321
|
+
)))), isBugType && /* @__PURE__ */ import_react12.default.createElement(import_react_native11.View, { style: styles5.section }, /* @__PURE__ */ import_react12.default.createElement(import_react_native11.Text, { style: shared.label }, "Which screen?"), /* @__PURE__ */ import_react12.default.createElement(
|
|
17101
17322
|
import_react_native11.TextInput,
|
|
17102
17323
|
{
|
|
17103
17324
|
style: styles5.screenInput,
|
|
@@ -17178,6 +17399,19 @@ var import_react_native13 = require("react-native");
|
|
|
17178
17399
|
function MessageListScreen({ nav }) {
|
|
17179
17400
|
const { threads, unreadCount, refreshThreads, dashboardUrl, isLoading, widgetColorScheme } = useBugBear();
|
|
17180
17401
|
const styles5 = (0, import_react14.useMemo)(() => createStyles7(), [widgetColorScheme]);
|
|
17402
|
+
const [activeFilter, setActiveFilter] = (0, import_react14.useState)("all");
|
|
17403
|
+
const filteredThreads = threads.filter((thread) => {
|
|
17404
|
+
if (activeFilter === "all") return true;
|
|
17405
|
+
if (activeFilter === "unread") return thread.unreadCount > 0;
|
|
17406
|
+
return thread.threadType === activeFilter;
|
|
17407
|
+
});
|
|
17408
|
+
const filterChips = [
|
|
17409
|
+
{ key: "all", label: "All" },
|
|
17410
|
+
{ key: "report", label: "Bug Reports" },
|
|
17411
|
+
{ key: "direct", label: "Direct" },
|
|
17412
|
+
{ key: "announcement", label: "Announcements" },
|
|
17413
|
+
{ key: "unread", label: "Unread", count: threads.filter((t) => t.unreadCount > 0).length }
|
|
17414
|
+
];
|
|
17181
17415
|
if (isLoading) return /* @__PURE__ */ import_react14.default.createElement(MessageListScreenSkeleton, null);
|
|
17182
17416
|
return /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, null, /* @__PURE__ */ import_react14.default.createElement(
|
|
17183
17417
|
import_react_native13.TouchableOpacity,
|
|
@@ -17186,14 +17420,54 @@ function MessageListScreen({ nav }) {
|
|
|
17186
17420
|
onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
|
|
17187
17421
|
},
|
|
17188
17422
|
/* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.newMsgText }, "\u2709\uFE0F New Message")
|
|
17189
|
-
),
|
|
17423
|
+
), /* @__PURE__ */ import_react14.default.createElement(
|
|
17424
|
+
import_react_native13.ScrollView,
|
|
17425
|
+
{
|
|
17426
|
+
horizontal: true,
|
|
17427
|
+
showsHorizontalScrollIndicator: false,
|
|
17428
|
+
style: { paddingBottom: 12 },
|
|
17429
|
+
contentContainerStyle: { paddingHorizontal: 16, gap: 8 }
|
|
17430
|
+
},
|
|
17431
|
+
filterChips.map((chip) => /* @__PURE__ */ import_react14.default.createElement(
|
|
17432
|
+
import_react_native13.TouchableOpacity,
|
|
17433
|
+
{
|
|
17434
|
+
key: chip.key,
|
|
17435
|
+
onPress: () => setActiveFilter(chip.key),
|
|
17436
|
+
style: {
|
|
17437
|
+
paddingVertical: 6,
|
|
17438
|
+
paddingHorizontal: 12,
|
|
17439
|
+
borderRadius: 16,
|
|
17440
|
+
borderWidth: 1,
|
|
17441
|
+
borderColor: activeFilter === chip.key ? colors.blue : colors.border,
|
|
17442
|
+
backgroundColor: activeFilter === chip.key ? withAlpha(colors.blue, 0.15) : "transparent",
|
|
17443
|
+
flexDirection: "row",
|
|
17444
|
+
alignItems: "center",
|
|
17445
|
+
gap: 4
|
|
17446
|
+
}
|
|
17447
|
+
},
|
|
17448
|
+
/* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: {
|
|
17449
|
+
fontSize: 12,
|
|
17450
|
+
fontWeight: "500",
|
|
17451
|
+
color: activeFilter === chip.key ? colors.blue : colors.textSecondary
|
|
17452
|
+
} }, chip.label),
|
|
17453
|
+
chip.count !== void 0 && chip.count > 0 && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: {
|
|
17454
|
+
backgroundColor: colors.blue,
|
|
17455
|
+
borderRadius: 8,
|
|
17456
|
+
minWidth: 16,
|
|
17457
|
+
height: 16,
|
|
17458
|
+
justifyContent: "center",
|
|
17459
|
+
alignItems: "center",
|
|
17460
|
+
paddingHorizontal: 4
|
|
17461
|
+
} }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: { fontSize: 10, fontWeight: "bold", color: colors.onPrimary } }, chip.count))
|
|
17462
|
+
))
|
|
17463
|
+
), filteredThreads.length === 0 ? /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: shared.emptyState }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: shared.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: shared.emptyTitle }, "No messages yet"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: shared.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, null, filteredThreads.map((thread) => /* @__PURE__ */ import_react14.default.createElement(
|
|
17190
17464
|
import_react_native13.TouchableOpacity,
|
|
17191
17465
|
{
|
|
17192
17466
|
key: thread.id,
|
|
17193
17467
|
style: [styles5.threadItem, thread.unreadCount > 0 && styles5.threadItemUnread],
|
|
17194
17468
|
onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
|
|
17195
17469
|
},
|
|
17196
|
-
/* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadLeft }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadInfo }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadTitleRow }, thread.isPinned && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.lastMessage && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
|
|
17470
|
+
/* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadLeft }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadInfo }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadTitleRow }, thread.isPinned && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.threadType === "report" && thread.reporterName && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: { fontSize: 11, color: colors.textMuted } }, "Reported by: ", thread.reporterName), thread.lastMessage && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
|
|
17197
17471
|
/* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.threadRight }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.threadTime }, formatRelativeTime(thread.lastMessageAt)), thread.unreadCount > 0 && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.unreadBadge }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.unreadText }, thread.unreadCount)), thread.priority !== "normal" && /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: [styles5.priorityDot, { backgroundColor: getPriorityColor(thread.priority) }] }))
|
|
17198
17472
|
))), dashboardUrl && /* @__PURE__ */ import_react14.default.createElement(
|
|
17199
17473
|
import_react_native13.TouchableOpacity,
|
|
@@ -17203,7 +17477,7 @@ function MessageListScreen({ nav }) {
|
|
|
17203
17477
|
activeOpacity: 0.7
|
|
17204
17478
|
},
|
|
17205
17479
|
/* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.dashboardLinkText }, "\u{1F310}", " View on Dashboard ", "\u2192")
|
|
17206
|
-
), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.footer }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.footerText },
|
|
17480
|
+
), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, { style: styles5.footer }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.footerText }, filteredThreads.length, " thread", filteredThreads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ import_react14.default.createElement(import_react_native13.TouchableOpacity, { onPress: refreshThreads }, /* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.refreshText }, "\u21BB Refresh"))));
|
|
17207
17481
|
}
|
|
17208
17482
|
function createStyles7() {
|
|
17209
17483
|
return import_react_native13.StyleSheet.create({
|
|
@@ -17584,7 +17858,17 @@ function IssueListScreen({ nav, category }) {
|
|
|
17584
17858
|
const [loading, setLoading] = (0, import_react18.useState)(true);
|
|
17585
17859
|
const [counts, setCounts] = (0, import_react18.useState)(null);
|
|
17586
17860
|
const [sortMode, setSortMode] = (0, import_react18.useState)("severity");
|
|
17861
|
+
const [searchQuery, setSearchQuery] = (0, import_react18.useState)("");
|
|
17862
|
+
const [debouncedQuery, setDebouncedQuery] = (0, import_react18.useState)("");
|
|
17587
17863
|
const config = CATEGORY_CONFIG[activeCategory];
|
|
17864
|
+
(0, import_react18.useEffect)(() => {
|
|
17865
|
+
const timer = setTimeout(() => setDebouncedQuery(searchQuery), 300);
|
|
17866
|
+
return () => clearTimeout(timer);
|
|
17867
|
+
}, [searchQuery]);
|
|
17868
|
+
(0, import_react18.useEffect)(() => {
|
|
17869
|
+
setSearchQuery("");
|
|
17870
|
+
setDebouncedQuery("");
|
|
17871
|
+
}, [activeCategory]);
|
|
17588
17872
|
(0, import_react18.useEffect)(() => {
|
|
17589
17873
|
if (!client) return;
|
|
17590
17874
|
client.getIssueCounts().then(setCounts).catch(() => {
|
|
@@ -17624,6 +17908,9 @@ function IssueListScreen({ nav, category }) {
|
|
|
17624
17908
|
}
|
|
17625
17909
|
return sorted;
|
|
17626
17910
|
}, [issues, sortMode]);
|
|
17911
|
+
const searchFilteredIssues = debouncedQuery ? sortedIssues.filter(
|
|
17912
|
+
(issue) => (issue.title || "").toLowerCase().includes(debouncedQuery.toLowerCase()) || (issue.description || "").toLowerCase().includes(debouncedQuery.toLowerCase())
|
|
17913
|
+
) : sortedIssues;
|
|
17627
17914
|
return /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, null, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles5.tabBar }, CATEGORIES.map((cat) => {
|
|
17628
17915
|
const catConfig = CATEGORY_CONFIG[cat];
|
|
17629
17916
|
const isActive = activeCategory === cat;
|
|
@@ -17670,7 +17957,25 @@ function IssueListScreen({ nav, category }) {
|
|
|
17670
17957
|
styles5.sortBtnText,
|
|
17671
17958
|
sortMode === s2.key && styles5.sortBtnTextActive
|
|
17672
17959
|
] }, s2.label)
|
|
17673
|
-
))),
|
|
17960
|
+
))), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: { paddingHorizontal: 16, paddingBottom: 12 } }, /* @__PURE__ */ import_react18.default.createElement(
|
|
17961
|
+
import_react_native17.TextInput,
|
|
17962
|
+
{
|
|
17963
|
+
value: searchQuery,
|
|
17964
|
+
onChangeText: setSearchQuery,
|
|
17965
|
+
placeholder: "Search my reports...",
|
|
17966
|
+
placeholderTextColor: colors.textMuted,
|
|
17967
|
+
style: {
|
|
17968
|
+
padding: 8,
|
|
17969
|
+
paddingHorizontal: 12,
|
|
17970
|
+
borderRadius: 8,
|
|
17971
|
+
borderWidth: 1,
|
|
17972
|
+
borderColor: colors.border,
|
|
17973
|
+
backgroundColor: colors.card,
|
|
17974
|
+
color: colors.textPrimary,
|
|
17975
|
+
fontSize: 13
|
|
17976
|
+
}
|
|
17977
|
+
}
|
|
17978
|
+
)), loading ? /* @__PURE__ */ import_react18.default.createElement(IssueListScreenSkeleton, null) : searchFilteredIssues.length === 0 ? /* @__PURE__ */ import_react18.default.createElement(import_react_native17.View, { style: styles5.emptyContainer }, /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles5.emptyIcon }, debouncedQuery ? "\u{1F50D}" : config.emptyIcon), /* @__PURE__ */ import_react18.default.createElement(import_react_native17.Text, { style: styles5.emptyText }, debouncedQuery ? "No matching issues" : config.emptyText)) : searchFilteredIssues.map((issue) => /* @__PURE__ */ import_react18.default.createElement(
|
|
17674
17979
|
import_react_native17.TouchableOpacity,
|
|
17675
17980
|
{
|
|
17676
17981
|
key: issue.id,
|
|
@@ -18196,11 +18501,13 @@ function SessionStartScreen({ nav }) {
|
|
|
18196
18501
|
const styles5 = (0, import_react20.useMemo)(() => createStyles13(), [widgetColorScheme]);
|
|
18197
18502
|
const [focusArea, setFocusArea] = (0, import_react20.useState)("");
|
|
18198
18503
|
const [isStarting, setIsStarting] = (0, import_react20.useState)(false);
|
|
18504
|
+
const [error, setError] = (0, import_react20.useState)(null);
|
|
18199
18505
|
const trackNames = Array.from(new Set(
|
|
18200
18506
|
assignments.filter((a) => a.testCase.track?.name).map((a) => a.testCase.track.name)
|
|
18201
18507
|
)).slice(0, 6);
|
|
18202
18508
|
const handleStart = async () => {
|
|
18203
18509
|
if (isStarting) return;
|
|
18510
|
+
setError(null);
|
|
18204
18511
|
import_react_native19.Keyboard.dismiss();
|
|
18205
18512
|
setIsStarting(true);
|
|
18206
18513
|
try {
|
|
@@ -18209,6 +18516,10 @@ function SessionStartScreen({ nav }) {
|
|
|
18209
18516
|
});
|
|
18210
18517
|
if (result.success) {
|
|
18211
18518
|
nav.replace({ name: "SESSION_ACTIVE" });
|
|
18519
|
+
} else {
|
|
18520
|
+
const msg = result.error || "Failed to start session. Please try again.";
|
|
18521
|
+
console.warn("BugBear: Session start failed:", msg);
|
|
18522
|
+
setError(msg);
|
|
18212
18523
|
}
|
|
18213
18524
|
} finally {
|
|
18214
18525
|
setIsStarting(false);
|
|
@@ -18234,7 +18545,7 @@ function SessionStartScreen({ nav }) {
|
|
|
18234
18545
|
activeOpacity: 0.7
|
|
18235
18546
|
},
|
|
18236
18547
|
/* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: [styles5.chipText, focusArea === name && styles5.chipTextActive] }, name)
|
|
18237
|
-
)))), /* @__PURE__ */ import_react20.default.createElement(
|
|
18548
|
+
)))), error && /* @__PURE__ */ import_react20.default.createElement(import_react_native19.View, { style: styles5.errorBox }, /* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: styles5.errorText }, error)), /* @__PURE__ */ import_react20.default.createElement(
|
|
18238
18549
|
import_react_native19.TouchableOpacity,
|
|
18239
18550
|
{
|
|
18240
18551
|
style: [styles5.startBtn, isStarting && styles5.startBtnDisabled],
|
|
@@ -18313,6 +18624,20 @@ function createStyles13() {
|
|
|
18313
18624
|
chipTextActive: {
|
|
18314
18625
|
color: colors.blueLight
|
|
18315
18626
|
},
|
|
18627
|
+
errorBox: {
|
|
18628
|
+
marginBottom: 12,
|
|
18629
|
+
paddingVertical: 10,
|
|
18630
|
+
paddingHorizontal: 12,
|
|
18631
|
+
backgroundColor: "rgba(239, 68, 68, 0.1)",
|
|
18632
|
+
borderWidth: 1,
|
|
18633
|
+
borderColor: "rgba(239, 68, 68, 0.3)",
|
|
18634
|
+
borderRadius: 8
|
|
18635
|
+
},
|
|
18636
|
+
errorText: {
|
|
18637
|
+
fontSize: 13,
|
|
18638
|
+
color: "#f87171",
|
|
18639
|
+
lineHeight: 18
|
|
18640
|
+
},
|
|
18316
18641
|
startBtn: {
|
|
18317
18642
|
paddingVertical: 14,
|
|
18318
18643
|
backgroundColor: colors.blueSurface,
|
package/dist/index.mjs
CHANGED
|
@@ -12202,6 +12202,9 @@ var BugBearClient = class {
|
|
|
12202
12202
|
this.monitor = null;
|
|
12203
12203
|
this.initialized = false;
|
|
12204
12204
|
this.initError = null;
|
|
12205
|
+
this._presenceSessionId = null;
|
|
12206
|
+
this._presenceInterval = null;
|
|
12207
|
+
this._presencePaused = false;
|
|
12205
12208
|
this.config = config;
|
|
12206
12209
|
if (config.apiKey) {
|
|
12207
12210
|
this.pendingInit = this.resolveFromApiKey(config.apiKey);
|
|
@@ -13847,6 +13850,7 @@ var BugBearClient = class {
|
|
|
13847
13850
|
lastMessageAt: row.last_message_at,
|
|
13848
13851
|
createdAt: row.created_at,
|
|
13849
13852
|
unreadCount: Number(row.unread_count) || 0,
|
|
13853
|
+
reporterName: row.reporter_name || void 0,
|
|
13850
13854
|
lastMessage: row.last_message_preview ? {
|
|
13851
13855
|
id: "",
|
|
13852
13856
|
threadId: row.thread_id,
|
|
@@ -14286,6 +14290,93 @@ var BugBearClient = class {
|
|
|
14286
14290
|
updatedAt: data.updated_at
|
|
14287
14291
|
};
|
|
14288
14292
|
}
|
|
14293
|
+
// ─── Passive Presence Tracking ──────────────────────────────
|
|
14294
|
+
/** Current presence session ID (null if not tracking). */
|
|
14295
|
+
get presenceSessionId() {
|
|
14296
|
+
return this._presenceSessionId;
|
|
14297
|
+
}
|
|
14298
|
+
/**
|
|
14299
|
+
* Start passive presence tracking for this tester.
|
|
14300
|
+
* Idempotent — reuses an existing active session if one exists.
|
|
14301
|
+
*/
|
|
14302
|
+
async startPresence(platform) {
|
|
14303
|
+
try {
|
|
14304
|
+
await this.ensureReady();
|
|
14305
|
+
const testerInfo = await this.getTesterInfo();
|
|
14306
|
+
if (!testerInfo) return null;
|
|
14307
|
+
const { data, error } = await this.supabase.rpc("upsert_tester_presence", {
|
|
14308
|
+
p_project_id: this.config.projectId,
|
|
14309
|
+
p_tester_id: testerInfo.id,
|
|
14310
|
+
p_platform: platform
|
|
14311
|
+
});
|
|
14312
|
+
if (error) {
|
|
14313
|
+
console.error("BugBear: Failed to start presence", formatPgError(error));
|
|
14314
|
+
return null;
|
|
14315
|
+
}
|
|
14316
|
+
this._presenceSessionId = data;
|
|
14317
|
+
this._presencePaused = false;
|
|
14318
|
+
this.startPresenceHeartbeat();
|
|
14319
|
+
return data;
|
|
14320
|
+
} catch (err) {
|
|
14321
|
+
console.error("BugBear: Error starting presence", err);
|
|
14322
|
+
return null;
|
|
14323
|
+
}
|
|
14324
|
+
}
|
|
14325
|
+
/** Gracefully end the current presence session. */
|
|
14326
|
+
async endPresence() {
|
|
14327
|
+
this.stopPresenceHeartbeat();
|
|
14328
|
+
if (!this._presenceSessionId) return;
|
|
14329
|
+
try {
|
|
14330
|
+
await this.supabase.rpc("end_tester_presence", {
|
|
14331
|
+
p_session_id: this._presenceSessionId
|
|
14332
|
+
});
|
|
14333
|
+
} catch {
|
|
14334
|
+
}
|
|
14335
|
+
this._presenceSessionId = null;
|
|
14336
|
+
}
|
|
14337
|
+
/** Pause heartbeat (tab hidden / app backgrounded). Sends one final beat. */
|
|
14338
|
+
pausePresence() {
|
|
14339
|
+
this._presencePaused = true;
|
|
14340
|
+
this.heartbeatPresence();
|
|
14341
|
+
}
|
|
14342
|
+
/** Resume heartbeat after pause. Restarts if session was cleaned up. */
|
|
14343
|
+
async resumePresence() {
|
|
14344
|
+
if (!this._presenceSessionId) return;
|
|
14345
|
+
this._presencePaused = false;
|
|
14346
|
+
try {
|
|
14347
|
+
const { data } = await this.supabase.rpc("heartbeat_tester_presence", {
|
|
14348
|
+
p_session_id: this._presenceSessionId
|
|
14349
|
+
});
|
|
14350
|
+
if (!data) {
|
|
14351
|
+
this._presenceSessionId = null;
|
|
14352
|
+
}
|
|
14353
|
+
} catch {
|
|
14354
|
+
this._presenceSessionId = null;
|
|
14355
|
+
}
|
|
14356
|
+
}
|
|
14357
|
+
async heartbeatPresence() {
|
|
14358
|
+
if (!this._presenceSessionId || this._presencePaused) return;
|
|
14359
|
+
try {
|
|
14360
|
+
const { data, error } = await this.supabase.rpc("heartbeat_tester_presence", {
|
|
14361
|
+
p_session_id: this._presenceSessionId
|
|
14362
|
+
});
|
|
14363
|
+
if (error || data === false) {
|
|
14364
|
+
this.stopPresenceHeartbeat();
|
|
14365
|
+
this._presenceSessionId = null;
|
|
14366
|
+
}
|
|
14367
|
+
} catch {
|
|
14368
|
+
}
|
|
14369
|
+
}
|
|
14370
|
+
startPresenceHeartbeat() {
|
|
14371
|
+
this.stopPresenceHeartbeat();
|
|
14372
|
+
this._presenceInterval = setInterval(() => this.heartbeatPresence(), 6e4);
|
|
14373
|
+
}
|
|
14374
|
+
stopPresenceHeartbeat() {
|
|
14375
|
+
if (this._presenceInterval) {
|
|
14376
|
+
clearInterval(this._presenceInterval);
|
|
14377
|
+
this._presenceInterval = null;
|
|
14378
|
+
}
|
|
14379
|
+
}
|
|
14289
14380
|
};
|
|
14290
14381
|
function createBugBear(config) {
|
|
14291
14382
|
return new BugBearClient(config);
|
|
@@ -14420,6 +14511,9 @@ function setActiveColors(palette) {
|
|
|
14420
14511
|
colors = palette;
|
|
14421
14512
|
shared = createSharedStyles();
|
|
14422
14513
|
}
|
|
14514
|
+
function withAlpha(hex, alpha) {
|
|
14515
|
+
return hex + Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
14516
|
+
}
|
|
14423
14517
|
function createSharedStyles() {
|
|
14424
14518
|
return StyleSheet.create({
|
|
14425
14519
|
card: {
|
|
@@ -14922,6 +15016,28 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14922
15016
|
}
|
|
14923
15017
|
return () => subscription.remove();
|
|
14924
15018
|
}, [client]);
|
|
15019
|
+
useEffect(() => {
|
|
15020
|
+
if (!client || !isTester) return;
|
|
15021
|
+
let mounted = true;
|
|
15022
|
+
const platform = Platform2.OS === "ios" ? "ios" : "android";
|
|
15023
|
+
client.startPresence(platform);
|
|
15024
|
+
const subscription = AppState.addEventListener("change", (nextState) => {
|
|
15025
|
+
if (nextState === "active") {
|
|
15026
|
+
client.resumePresence().then(() => {
|
|
15027
|
+
if (mounted && !client.presenceSessionId) {
|
|
15028
|
+
client.startPresence(platform);
|
|
15029
|
+
}
|
|
15030
|
+
});
|
|
15031
|
+
} else if (nextState === "background" || nextState === "inactive") {
|
|
15032
|
+
client.pausePresence();
|
|
15033
|
+
}
|
|
15034
|
+
});
|
|
15035
|
+
return () => {
|
|
15036
|
+
mounted = false;
|
|
15037
|
+
subscription.remove();
|
|
15038
|
+
client.endPresence();
|
|
15039
|
+
};
|
|
15040
|
+
}, [client, isTester]);
|
|
14925
15041
|
useEffect(() => {
|
|
14926
15042
|
if (!client || !isTester) return;
|
|
14927
15043
|
if (widgetMode === "qa" && !isQAEnabled) return;
|
|
@@ -15017,14 +15133,14 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
15017
15133
|
}
|
|
15018
15134
|
|
|
15019
15135
|
// src/BugBearButton.tsx
|
|
15020
|
-
import React21, { useState as
|
|
15136
|
+
import React21, { useState as useState18, useRef as useRef6, useMemo as useMemo16 } from "react";
|
|
15021
15137
|
import {
|
|
15022
15138
|
View as View21,
|
|
15023
15139
|
Text as Text19,
|
|
15024
15140
|
Image as Image4,
|
|
15025
15141
|
TouchableOpacity as TouchableOpacity18,
|
|
15026
15142
|
Modal as Modal3,
|
|
15027
|
-
ScrollView as
|
|
15143
|
+
ScrollView as ScrollView4,
|
|
15028
15144
|
StyleSheet as StyleSheet21,
|
|
15029
15145
|
Dimensions as Dimensions2,
|
|
15030
15146
|
KeyboardAvoidingView,
|
|
@@ -16934,6 +17050,8 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16934
17050
|
const [severity, setSeverity] = useState8("medium");
|
|
16935
17051
|
const [category, setCategory] = useState8(null);
|
|
16936
17052
|
const [description, setDescription] = useState8("");
|
|
17053
|
+
const [myIssues, setMyIssues] = useState8([]);
|
|
17054
|
+
const [similarReports, setSimilarReports] = useState8([]);
|
|
16937
17055
|
const [affectedScreen, setAffectedScreen] = useState8("");
|
|
16938
17056
|
const [submitting, setSubmitting] = useState8(false);
|
|
16939
17057
|
const [error, setError] = useState8(null);
|
|
@@ -16949,6 +17067,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16949
17067
|
}, [autoCaptureUri, onAutoCaptureConsumed]);
|
|
16950
17068
|
const isRetestFailure = prefill?.type === "test_fail";
|
|
16951
17069
|
const isBugType = reportType === "bug" || reportType === "test_fail";
|
|
17070
|
+
useEffect6(() => {
|
|
17071
|
+
if (!client?.getIssues) return;
|
|
17072
|
+
let cancelled = false;
|
|
17073
|
+
const load = async () => {
|
|
17074
|
+
try {
|
|
17075
|
+
const [open, done] = await Promise.all([
|
|
17076
|
+
client.getIssues("open"),
|
|
17077
|
+
client.getIssues("done")
|
|
17078
|
+
]);
|
|
17079
|
+
if (!cancelled) setMyIssues([...open, ...done]);
|
|
17080
|
+
} catch {
|
|
17081
|
+
}
|
|
17082
|
+
};
|
|
17083
|
+
load();
|
|
17084
|
+
return () => {
|
|
17085
|
+
cancelled = true;
|
|
17086
|
+
};
|
|
17087
|
+
}, [client]);
|
|
17088
|
+
useEffect6(() => {
|
|
17089
|
+
if (description.length < 10 || myIssues.length === 0) {
|
|
17090
|
+
setSimilarReports([]);
|
|
17091
|
+
return;
|
|
17092
|
+
}
|
|
17093
|
+
const words = description.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
17094
|
+
if (words.length === 0) {
|
|
17095
|
+
setSimilarReports([]);
|
|
17096
|
+
return;
|
|
17097
|
+
}
|
|
17098
|
+
const scored = myIssues.map((issue) => {
|
|
17099
|
+
const text = `${issue.title || ""} ${issue.description || ""}`.toLowerCase();
|
|
17100
|
+
const matches = words.filter((w) => text.includes(w)).length;
|
|
17101
|
+
return { issue, score: matches / words.length };
|
|
17102
|
+
}).filter((s2) => s2.score >= 0.3).sort((a, b) => b.score - a.score).slice(0, 3);
|
|
17103
|
+
setSimilarReports(scored.map((s2) => s2.issue));
|
|
17104
|
+
}, [description, myIssues]);
|
|
16952
17105
|
useEffect6(() => {
|
|
16953
17106
|
if (reportType === "feedback" || reportType === "suggestion") {
|
|
16954
17107
|
setCategory("other");
|
|
@@ -17023,7 +17176,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
17023
17176
|
numberOfLines: 4,
|
|
17024
17177
|
textAlignVertical: "top"
|
|
17025
17178
|
}
|
|
17026
|
-
)
|
|
17179
|
+
), similarReports.length > 0 && /* @__PURE__ */ React10.createElement(View10, { style: {
|
|
17180
|
+
marginTop: 8,
|
|
17181
|
+
padding: 12,
|
|
17182
|
+
borderRadius: 8,
|
|
17183
|
+
backgroundColor: withAlpha(colors.orange, 0.1),
|
|
17184
|
+
borderWidth: 1,
|
|
17185
|
+
borderColor: withAlpha(colors.orange, 0.3)
|
|
17186
|
+
} }, /* @__PURE__ */ React10.createElement(Text8, { style: { fontSize: 12, fontWeight: "600", color: colors.orange, marginBottom: 8 } }, "Similar reports you've already filed:"), similarReports.map((issue) => /* @__PURE__ */ React10.createElement(
|
|
17187
|
+
TouchableOpacity8,
|
|
17188
|
+
{
|
|
17189
|
+
key: issue.id,
|
|
17190
|
+
onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
|
|
17191
|
+
style: {
|
|
17192
|
+
flexDirection: "row",
|
|
17193
|
+
alignItems: "center",
|
|
17194
|
+
gap: 8,
|
|
17195
|
+
paddingVertical: 6
|
|
17196
|
+
}
|
|
17197
|
+
},
|
|
17198
|
+
/* @__PURE__ */ React10.createElement(View10, { style: {
|
|
17199
|
+
width: 8,
|
|
17200
|
+
height: 8,
|
|
17201
|
+
borderRadius: 4,
|
|
17202
|
+
backgroundColor: issue.severity === "critical" ? colors.red : issue.severity === "high" ? colors.orange : colors.yellow
|
|
17203
|
+
} }),
|
|
17204
|
+
/* @__PURE__ */ React10.createElement(
|
|
17205
|
+
Text8,
|
|
17206
|
+
{
|
|
17207
|
+
numberOfLines: 1,
|
|
17208
|
+
style: { fontSize: 12, color: colors.textPrimary, flex: 1 }
|
|
17209
|
+
},
|
|
17210
|
+
issue.title
|
|
17211
|
+
),
|
|
17212
|
+
/* @__PURE__ */ React10.createElement(Text8, { style: { fontSize: 11, color: colors.textMuted } }, issue.status)
|
|
17213
|
+
)))), /* @__PURE__ */ React10.createElement(
|
|
17027
17214
|
ImagePickerButtons,
|
|
17028
17215
|
{
|
|
17029
17216
|
images: images.images,
|
|
@@ -17079,7 +17266,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
17079
17266
|
numberOfLines: 4,
|
|
17080
17267
|
textAlignVertical: "top"
|
|
17081
17268
|
}
|
|
17082
|
-
)
|
|
17269
|
+
), similarReports.length > 0 && /* @__PURE__ */ React10.createElement(View10, { style: {
|
|
17270
|
+
marginTop: 8,
|
|
17271
|
+
padding: 12,
|
|
17272
|
+
borderRadius: 8,
|
|
17273
|
+
backgroundColor: withAlpha(colors.orange, 0.1),
|
|
17274
|
+
borderWidth: 1,
|
|
17275
|
+
borderColor: withAlpha(colors.orange, 0.3)
|
|
17276
|
+
} }, /* @__PURE__ */ React10.createElement(Text8, { style: { fontSize: 12, fontWeight: "600", color: colors.orange, marginBottom: 8 } }, "Similar reports you've already filed:"), similarReports.map((issue) => /* @__PURE__ */ React10.createElement(
|
|
17277
|
+
TouchableOpacity8,
|
|
17278
|
+
{
|
|
17279
|
+
key: issue.id,
|
|
17280
|
+
onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
|
|
17281
|
+
style: {
|
|
17282
|
+
flexDirection: "row",
|
|
17283
|
+
alignItems: "center",
|
|
17284
|
+
gap: 8,
|
|
17285
|
+
paddingVertical: 6
|
|
17286
|
+
}
|
|
17287
|
+
},
|
|
17288
|
+
/* @__PURE__ */ React10.createElement(View10, { style: {
|
|
17289
|
+
width: 8,
|
|
17290
|
+
height: 8,
|
|
17291
|
+
borderRadius: 4,
|
|
17292
|
+
backgroundColor: issue.severity === "critical" ? colors.red : issue.severity === "high" ? colors.orange : colors.yellow
|
|
17293
|
+
} }),
|
|
17294
|
+
/* @__PURE__ */ React10.createElement(
|
|
17295
|
+
Text8,
|
|
17296
|
+
{
|
|
17297
|
+
numberOfLines: 1,
|
|
17298
|
+
style: { fontSize: 12, color: colors.textPrimary, flex: 1 }
|
|
17299
|
+
},
|
|
17300
|
+
issue.title
|
|
17301
|
+
),
|
|
17302
|
+
/* @__PURE__ */ React10.createElement(Text8, { style: { fontSize: 11, color: colors.textMuted } }, issue.status)
|
|
17303
|
+
)))), isBugType && /* @__PURE__ */ React10.createElement(View10, { style: styles5.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Which screen?"), /* @__PURE__ */ React10.createElement(
|
|
17083
17304
|
TextInput4,
|
|
17084
17305
|
{
|
|
17085
17306
|
style: styles5.screenInput,
|
|
@@ -17155,11 +17376,24 @@ function createStyles6() {
|
|
|
17155
17376
|
}
|
|
17156
17377
|
|
|
17157
17378
|
// src/widget/screens/MessageListScreen.tsx
|
|
17158
|
-
import React12, { useMemo as useMemo7 } from "react";
|
|
17159
|
-
import { View as View12, Text as Text10, TouchableOpacity as TouchableOpacity9, StyleSheet as StyleSheet12, Linking as Linking3 } from "react-native";
|
|
17379
|
+
import React12, { useMemo as useMemo7, useState as useState9 } from "react";
|
|
17380
|
+
import { View as View12, Text as Text10, TouchableOpacity as TouchableOpacity9, ScrollView as ScrollView3, StyleSheet as StyleSheet12, Linking as Linking3 } from "react-native";
|
|
17160
17381
|
function MessageListScreen({ nav }) {
|
|
17161
17382
|
const { threads, unreadCount, refreshThreads, dashboardUrl, isLoading, widgetColorScheme } = useBugBear();
|
|
17162
17383
|
const styles5 = useMemo7(() => createStyles7(), [widgetColorScheme]);
|
|
17384
|
+
const [activeFilter, setActiveFilter] = useState9("all");
|
|
17385
|
+
const filteredThreads = threads.filter((thread) => {
|
|
17386
|
+
if (activeFilter === "all") return true;
|
|
17387
|
+
if (activeFilter === "unread") return thread.unreadCount > 0;
|
|
17388
|
+
return thread.threadType === activeFilter;
|
|
17389
|
+
});
|
|
17390
|
+
const filterChips = [
|
|
17391
|
+
{ key: "all", label: "All" },
|
|
17392
|
+
{ key: "report", label: "Bug Reports" },
|
|
17393
|
+
{ key: "direct", label: "Direct" },
|
|
17394
|
+
{ key: "announcement", label: "Announcements" },
|
|
17395
|
+
{ key: "unread", label: "Unread", count: threads.filter((t) => t.unreadCount > 0).length }
|
|
17396
|
+
];
|
|
17163
17397
|
if (isLoading) return /* @__PURE__ */ React12.createElement(MessageListScreenSkeleton, null);
|
|
17164
17398
|
return /* @__PURE__ */ React12.createElement(View12, null, /* @__PURE__ */ React12.createElement(
|
|
17165
17399
|
TouchableOpacity9,
|
|
@@ -17168,14 +17402,54 @@ function MessageListScreen({ nav }) {
|
|
|
17168
17402
|
onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
|
|
17169
17403
|
},
|
|
17170
17404
|
/* @__PURE__ */ React12.createElement(Text10, { style: styles5.newMsgText }, "\u2709\uFE0F New Message")
|
|
17171
|
-
),
|
|
17405
|
+
), /* @__PURE__ */ React12.createElement(
|
|
17406
|
+
ScrollView3,
|
|
17407
|
+
{
|
|
17408
|
+
horizontal: true,
|
|
17409
|
+
showsHorizontalScrollIndicator: false,
|
|
17410
|
+
style: { paddingBottom: 12 },
|
|
17411
|
+
contentContainerStyle: { paddingHorizontal: 16, gap: 8 }
|
|
17412
|
+
},
|
|
17413
|
+
filterChips.map((chip) => /* @__PURE__ */ React12.createElement(
|
|
17414
|
+
TouchableOpacity9,
|
|
17415
|
+
{
|
|
17416
|
+
key: chip.key,
|
|
17417
|
+
onPress: () => setActiveFilter(chip.key),
|
|
17418
|
+
style: {
|
|
17419
|
+
paddingVertical: 6,
|
|
17420
|
+
paddingHorizontal: 12,
|
|
17421
|
+
borderRadius: 16,
|
|
17422
|
+
borderWidth: 1,
|
|
17423
|
+
borderColor: activeFilter === chip.key ? colors.blue : colors.border,
|
|
17424
|
+
backgroundColor: activeFilter === chip.key ? withAlpha(colors.blue, 0.15) : "transparent",
|
|
17425
|
+
flexDirection: "row",
|
|
17426
|
+
alignItems: "center",
|
|
17427
|
+
gap: 4
|
|
17428
|
+
}
|
|
17429
|
+
},
|
|
17430
|
+
/* @__PURE__ */ React12.createElement(Text10, { style: {
|
|
17431
|
+
fontSize: 12,
|
|
17432
|
+
fontWeight: "500",
|
|
17433
|
+
color: activeFilter === chip.key ? colors.blue : colors.textSecondary
|
|
17434
|
+
} }, chip.label),
|
|
17435
|
+
chip.count !== void 0 && chip.count > 0 && /* @__PURE__ */ React12.createElement(View12, { style: {
|
|
17436
|
+
backgroundColor: colors.blue,
|
|
17437
|
+
borderRadius: 8,
|
|
17438
|
+
minWidth: 16,
|
|
17439
|
+
height: 16,
|
|
17440
|
+
justifyContent: "center",
|
|
17441
|
+
alignItems: "center",
|
|
17442
|
+
paddingHorizontal: 4
|
|
17443
|
+
} }, /* @__PURE__ */ React12.createElement(Text10, { style: { fontSize: 10, fontWeight: "bold", color: colors.onPrimary } }, chip.count))
|
|
17444
|
+
))
|
|
17445
|
+
), filteredThreads.length === 0 ? /* @__PURE__ */ React12.createElement(View12, { style: shared.emptyState }, /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptyTitle }, "No messages yet"), /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ React12.createElement(View12, null, filteredThreads.map((thread) => /* @__PURE__ */ React12.createElement(
|
|
17172
17446
|
TouchableOpacity9,
|
|
17173
17447
|
{
|
|
17174
17448
|
key: thread.id,
|
|
17175
17449
|
style: [styles5.threadItem, thread.unreadCount > 0 && styles5.threadItemUnread],
|
|
17176
17450
|
onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
|
|
17177
17451
|
},
|
|
17178
|
-
/* @__PURE__ */ React12.createElement(View12, { style: styles5.threadLeft }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadInfo }, /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadTitleRow }, thread.isPinned && /* @__PURE__ */ React12.createElement(Text10, { style: styles5.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.lastMessage && /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
|
|
17452
|
+
/* @__PURE__ */ React12.createElement(View12, { style: styles5.threadLeft }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadInfo }, /* @__PURE__ */ React12.createElement(View12, { style: styles5.threadTitleRow }, thread.isPinned && /* @__PURE__ */ React12.createElement(Text10, { style: styles5.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.threadType === "report" && thread.reporterName && /* @__PURE__ */ React12.createElement(Text10, { style: { fontSize: 11, color: colors.textMuted } }, "Reported by: ", thread.reporterName), thread.lastMessage && /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
|
|
17179
17453
|
/* @__PURE__ */ React12.createElement(View12, { style: styles5.threadRight }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.threadTime }, formatRelativeTime(thread.lastMessageAt)), thread.unreadCount > 0 && /* @__PURE__ */ React12.createElement(View12, { style: styles5.unreadBadge }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.unreadText }, thread.unreadCount)), thread.priority !== "normal" && /* @__PURE__ */ React12.createElement(View12, { style: [styles5.priorityDot, { backgroundColor: getPriorityColor(thread.priority) }] }))
|
|
17180
17454
|
))), dashboardUrl && /* @__PURE__ */ React12.createElement(
|
|
17181
17455
|
TouchableOpacity9,
|
|
@@ -17185,7 +17459,7 @@ function MessageListScreen({ nav }) {
|
|
|
17185
17459
|
activeOpacity: 0.7
|
|
17186
17460
|
},
|
|
17187
17461
|
/* @__PURE__ */ React12.createElement(Text10, { style: styles5.dashboardLinkText }, "\u{1F310}", " View on Dashboard ", "\u2192")
|
|
17188
|
-
), /* @__PURE__ */ React12.createElement(View12, { style: styles5.footer }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.footerText },
|
|
17462
|
+
), /* @__PURE__ */ React12.createElement(View12, { style: styles5.footer }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.footerText }, filteredThreads.length, " thread", filteredThreads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ React12.createElement(TouchableOpacity9, { onPress: refreshThreads }, /* @__PURE__ */ React12.createElement(Text10, { style: styles5.refreshText }, "\u21BB Refresh"))));
|
|
17189
17463
|
}
|
|
17190
17464
|
function createStyles7() {
|
|
17191
17465
|
return StyleSheet12.create({
|
|
@@ -17214,16 +17488,16 @@ function createStyles7() {
|
|
|
17214
17488
|
}
|
|
17215
17489
|
|
|
17216
17490
|
// src/widget/screens/ThreadDetailScreen.tsx
|
|
17217
|
-
import React13, { useState as
|
|
17491
|
+
import React13, { useState as useState10, useEffect as useEffect8, useMemo as useMemo8 } from "react";
|
|
17218
17492
|
import { View as View13, Text as Text11, TouchableOpacity as TouchableOpacity10, TextInput as TextInput5, StyleSheet as StyleSheet13, Image as Image2 } from "react-native";
|
|
17219
17493
|
function ThreadDetailScreen({ thread, nav }) {
|
|
17220
17494
|
const { getThreadMessages, sendMessage, markAsRead, uploadImage, widgetColorScheme } = useBugBear();
|
|
17221
17495
|
const styles5 = useMemo8(() => createStyles8(), [widgetColorScheme]);
|
|
17222
|
-
const [messages, setMessages] =
|
|
17223
|
-
const [loading, setLoading] =
|
|
17224
|
-
const [replyText, setReplyText] =
|
|
17225
|
-
const [sending, setSending] =
|
|
17226
|
-
const [sendError, setSendError] =
|
|
17496
|
+
const [messages, setMessages] = useState10([]);
|
|
17497
|
+
const [loading, setLoading] = useState10(true);
|
|
17498
|
+
const [replyText, setReplyText] = useState10("");
|
|
17499
|
+
const [sending, setSending] = useState10(false);
|
|
17500
|
+
const [sendError, setSendError] = useState10(false);
|
|
17227
17501
|
const replyImages = useImageAttachments(uploadImage, 3, "discussion-attachments");
|
|
17228
17502
|
useEffect8(() => {
|
|
17229
17503
|
let cancelled = false;
|
|
@@ -17335,14 +17609,14 @@ function createStyles8() {
|
|
|
17335
17609
|
}
|
|
17336
17610
|
|
|
17337
17611
|
// src/widget/screens/ComposeMessageScreen.tsx
|
|
17338
|
-
import React14, { useState as
|
|
17612
|
+
import React14, { useState as useState11, useMemo as useMemo9 } from "react";
|
|
17339
17613
|
import { View as View14, Text as Text12, TextInput as TextInput6, TouchableOpacity as TouchableOpacity11, StyleSheet as StyleSheet14 } from "react-native";
|
|
17340
17614
|
function ComposeMessageScreen({ nav }) {
|
|
17341
17615
|
const { createThread, uploadImage, widgetColorScheme } = useBugBear();
|
|
17342
17616
|
const styles5 = useMemo9(() => createStyles9(), [widgetColorScheme]);
|
|
17343
|
-
const [subject, setSubject] =
|
|
17344
|
-
const [message, setMessage] =
|
|
17345
|
-
const [sending, setSending] =
|
|
17617
|
+
const [subject, setSubject] = useState11("");
|
|
17618
|
+
const [message, setMessage] = useState11("");
|
|
17619
|
+
const [sending, setSending] = useState11(false);
|
|
17346
17620
|
const images = useImageAttachments(uploadImage, 3, "discussion-attachments");
|
|
17347
17621
|
const handleSend = async () => {
|
|
17348
17622
|
if (!subject.trim() || !message.trim() || sending || images.isUploading) return;
|
|
@@ -17412,19 +17686,19 @@ function createStyles9() {
|
|
|
17412
17686
|
}
|
|
17413
17687
|
|
|
17414
17688
|
// src/widget/screens/ProfileScreen.tsx
|
|
17415
|
-
import React15, { useState as
|
|
17689
|
+
import React15, { useState as useState12, useEffect as useEffect9, useMemo as useMemo10 } from "react";
|
|
17416
17690
|
import { View as View15, Text as Text13, TouchableOpacity as TouchableOpacity12, TextInput as TextInput7, StyleSheet as StyleSheet15 } from "react-native";
|
|
17417
17691
|
function ProfileScreen({ nav }) {
|
|
17418
17692
|
const { testerInfo, assignments, updateTesterProfile, refreshTesterInfo, widgetColorScheme } = useBugBear();
|
|
17419
17693
|
const styles5 = useMemo10(() => createStyles10(), [widgetColorScheme]);
|
|
17420
|
-
const [editing, setEditing] =
|
|
17421
|
-
const [name, setName] =
|
|
17422
|
-
const [additionalEmails, setAdditionalEmails] =
|
|
17423
|
-
const [newEmailInput, setNewEmailInput] =
|
|
17424
|
-
const [platforms, setPlatforms] =
|
|
17425
|
-
const [saving, setSaving] =
|
|
17426
|
-
const [saved, setSaved] =
|
|
17427
|
-
const [showDetails, setShowDetails] =
|
|
17694
|
+
const [editing, setEditing] = useState12(false);
|
|
17695
|
+
const [name, setName] = useState12(testerInfo?.name || "");
|
|
17696
|
+
const [additionalEmails, setAdditionalEmails] = useState12(testerInfo?.additionalEmails || []);
|
|
17697
|
+
const [newEmailInput, setNewEmailInput] = useState12("");
|
|
17698
|
+
const [platforms, setPlatforms] = useState12(testerInfo?.platforms || []);
|
|
17699
|
+
const [saving, setSaving] = useState12(false);
|
|
17700
|
+
const [saved, setSaved] = useState12(false);
|
|
17701
|
+
const [showDetails, setShowDetails] = useState12(false);
|
|
17428
17702
|
const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
|
|
17429
17703
|
useEffect9(() => {
|
|
17430
17704
|
if (testerInfo) {
|
|
@@ -17543,8 +17817,8 @@ function createStyles10() {
|
|
|
17543
17817
|
}
|
|
17544
17818
|
|
|
17545
17819
|
// src/widget/screens/IssueListScreen.tsx
|
|
17546
|
-
import React16, { useState as
|
|
17547
|
-
import { View as View16, Text as Text14, TouchableOpacity as TouchableOpacity13, StyleSheet as StyleSheet16 } from "react-native";
|
|
17820
|
+
import React16, { useState as useState13, useEffect as useEffect10, useMemo as useMemo11 } from "react";
|
|
17821
|
+
import { View as View16, Text as Text14, TextInput as TextInput8, TouchableOpacity as TouchableOpacity13, StyleSheet as StyleSheet16 } from "react-native";
|
|
17548
17822
|
var CATEGORIES = ["open", "done", "reopened"];
|
|
17549
17823
|
var SEVERITY_ORDER2 = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17550
17824
|
function IssueListScreen({ nav, category }) {
|
|
@@ -17561,12 +17835,22 @@ function IssueListScreen({ nav, category }) {
|
|
|
17561
17835
|
medium: colors.yellow,
|
|
17562
17836
|
low: colors.textMuted
|
|
17563
17837
|
}), [widgetColorScheme]);
|
|
17564
|
-
const [activeCategory, setActiveCategory] =
|
|
17565
|
-
const [issues, setIssues] =
|
|
17566
|
-
const [loading, setLoading] =
|
|
17567
|
-
const [counts, setCounts] =
|
|
17568
|
-
const [sortMode, setSortMode] =
|
|
17838
|
+
const [activeCategory, setActiveCategory] = useState13(category);
|
|
17839
|
+
const [issues, setIssues] = useState13([]);
|
|
17840
|
+
const [loading, setLoading] = useState13(true);
|
|
17841
|
+
const [counts, setCounts] = useState13(null);
|
|
17842
|
+
const [sortMode, setSortMode] = useState13("severity");
|
|
17843
|
+
const [searchQuery, setSearchQuery] = useState13("");
|
|
17844
|
+
const [debouncedQuery, setDebouncedQuery] = useState13("");
|
|
17569
17845
|
const config = CATEGORY_CONFIG[activeCategory];
|
|
17846
|
+
useEffect10(() => {
|
|
17847
|
+
const timer = setTimeout(() => setDebouncedQuery(searchQuery), 300);
|
|
17848
|
+
return () => clearTimeout(timer);
|
|
17849
|
+
}, [searchQuery]);
|
|
17850
|
+
useEffect10(() => {
|
|
17851
|
+
setSearchQuery("");
|
|
17852
|
+
setDebouncedQuery("");
|
|
17853
|
+
}, [activeCategory]);
|
|
17570
17854
|
useEffect10(() => {
|
|
17571
17855
|
if (!client) return;
|
|
17572
17856
|
client.getIssueCounts().then(setCounts).catch(() => {
|
|
@@ -17606,6 +17890,9 @@ function IssueListScreen({ nav, category }) {
|
|
|
17606
17890
|
}
|
|
17607
17891
|
return sorted;
|
|
17608
17892
|
}, [issues, sortMode]);
|
|
17893
|
+
const searchFilteredIssues = debouncedQuery ? sortedIssues.filter(
|
|
17894
|
+
(issue) => (issue.title || "").toLowerCase().includes(debouncedQuery.toLowerCase()) || (issue.description || "").toLowerCase().includes(debouncedQuery.toLowerCase())
|
|
17895
|
+
) : sortedIssues;
|
|
17609
17896
|
return /* @__PURE__ */ React16.createElement(View16, null, /* @__PURE__ */ React16.createElement(View16, { style: styles5.tabBar }, CATEGORIES.map((cat) => {
|
|
17610
17897
|
const catConfig = CATEGORY_CONFIG[cat];
|
|
17611
17898
|
const isActive = activeCategory === cat;
|
|
@@ -17652,7 +17939,25 @@ function IssueListScreen({ nav, category }) {
|
|
|
17652
17939
|
styles5.sortBtnText,
|
|
17653
17940
|
sortMode === s2.key && styles5.sortBtnTextActive
|
|
17654
17941
|
] }, s2.label)
|
|
17655
|
-
))),
|
|
17942
|
+
))), /* @__PURE__ */ React16.createElement(View16, { style: { paddingHorizontal: 16, paddingBottom: 12 } }, /* @__PURE__ */ React16.createElement(
|
|
17943
|
+
TextInput8,
|
|
17944
|
+
{
|
|
17945
|
+
value: searchQuery,
|
|
17946
|
+
onChangeText: setSearchQuery,
|
|
17947
|
+
placeholder: "Search my reports...",
|
|
17948
|
+
placeholderTextColor: colors.textMuted,
|
|
17949
|
+
style: {
|
|
17950
|
+
padding: 8,
|
|
17951
|
+
paddingHorizontal: 12,
|
|
17952
|
+
borderRadius: 8,
|
|
17953
|
+
borderWidth: 1,
|
|
17954
|
+
borderColor: colors.border,
|
|
17955
|
+
backgroundColor: colors.card,
|
|
17956
|
+
color: colors.textPrimary,
|
|
17957
|
+
fontSize: 13
|
|
17958
|
+
}
|
|
17959
|
+
}
|
|
17960
|
+
)), loading ? /* @__PURE__ */ React16.createElement(IssueListScreenSkeleton, null) : searchFilteredIssues.length === 0 ? /* @__PURE__ */ React16.createElement(View16, { style: styles5.emptyContainer }, /* @__PURE__ */ React16.createElement(Text14, { style: styles5.emptyIcon }, debouncedQuery ? "\u{1F50D}" : config.emptyIcon), /* @__PURE__ */ React16.createElement(Text14, { style: styles5.emptyText }, debouncedQuery ? "No matching issues" : config.emptyText)) : searchFilteredIssues.map((issue) => /* @__PURE__ */ React16.createElement(
|
|
17656
17961
|
TouchableOpacity13,
|
|
17657
17962
|
{
|
|
17658
17963
|
key: issue.id,
|
|
@@ -17817,17 +18122,17 @@ function createStyles11() {
|
|
|
17817
18122
|
}
|
|
17818
18123
|
|
|
17819
18124
|
// src/widget/screens/IssueDetailScreen.tsx
|
|
17820
|
-
import React17, { useMemo as useMemo12, useState as
|
|
17821
|
-
import { View as View17, Text as Text15, Image as Image3, StyleSheet as StyleSheet17, Linking as Linking4, TouchableOpacity as TouchableOpacity14, TextInput as
|
|
18125
|
+
import React17, { useMemo as useMemo12, useState as useState14, useCallback as useCallback5 } from "react";
|
|
18126
|
+
import { View as View17, Text as Text15, Image as Image3, StyleSheet as StyleSheet17, Linking as Linking4, TouchableOpacity as TouchableOpacity14, TextInput as TextInput9, ActivityIndicator as ActivityIndicator2 } from "react-native";
|
|
17822
18127
|
var DONE_STATUSES = ["verified", "resolved", "closed", "reviewed"];
|
|
17823
18128
|
function IssueDetailScreen({ nav, issue }) {
|
|
17824
18129
|
const { dashboardUrl, widgetColorScheme, reopenReport } = useBugBear();
|
|
17825
18130
|
const styles5 = useMemo12(() => createStyles12(), [widgetColorScheme]);
|
|
17826
|
-
const [showReopenForm, setShowReopenForm] =
|
|
17827
|
-
const [reopenReason, setReopenReason] =
|
|
17828
|
-
const [isSubmitting, setIsSubmitting] =
|
|
17829
|
-
const [reopenError, setReopenError] =
|
|
17830
|
-
const [wasReopened, setWasReopened] =
|
|
18131
|
+
const [showReopenForm, setShowReopenForm] = useState14(false);
|
|
18132
|
+
const [reopenReason, setReopenReason] = useState14("");
|
|
18133
|
+
const [isSubmitting, setIsSubmitting] = useState14(false);
|
|
18134
|
+
const [reopenError, setReopenError] = useState14(null);
|
|
18135
|
+
const [wasReopened, setWasReopened] = useState14(false);
|
|
17831
18136
|
const STATUS_LABELS = useMemo12(() => ({
|
|
17832
18137
|
new: { label: "New", bg: colors.blueDark, color: colors.blueLight },
|
|
17833
18138
|
triaging: { label: "Triaging", bg: colors.blueDark, color: colors.blueLight },
|
|
@@ -17873,7 +18178,7 @@ function IssueDetailScreen({ nav, issue }) {
|
|
|
17873
18178
|
},
|
|
17874
18179
|
/* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenButtonText }, "\u{1F504}", " Not Fixed \u2014 Reopen Issue")
|
|
17875
18180
|
), showReopenForm && !wasReopened && /* @__PURE__ */ React17.createElement(View17, { style: styles5.reopenForm }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenFormTitle }, "Why isn't this fixed?"), /* @__PURE__ */ React17.createElement(
|
|
17876
|
-
|
|
18181
|
+
TextInput9,
|
|
17877
18182
|
{
|
|
17878
18183
|
value: reopenReason,
|
|
17879
18184
|
onChangeText: setReopenReason,
|
|
@@ -18171,18 +18476,20 @@ function createStyles12() {
|
|
|
18171
18476
|
}
|
|
18172
18477
|
|
|
18173
18478
|
// src/widget/screens/SessionStartScreen.tsx
|
|
18174
|
-
import React18, { useState as
|
|
18175
|
-
import { View as View18, Text as Text16, TextInput as
|
|
18479
|
+
import React18, { useState as useState15, useMemo as useMemo13 } from "react";
|
|
18480
|
+
import { View as View18, Text as Text16, TextInput as TextInput10, TouchableOpacity as TouchableOpacity15, StyleSheet as StyleSheet18, Keyboard as Keyboard2 } from "react-native";
|
|
18176
18481
|
function SessionStartScreen({ nav }) {
|
|
18177
18482
|
const { startSession, assignments, widgetColorScheme } = useBugBear();
|
|
18178
18483
|
const styles5 = useMemo13(() => createStyles13(), [widgetColorScheme]);
|
|
18179
|
-
const [focusArea, setFocusArea] =
|
|
18180
|
-
const [isStarting, setIsStarting] =
|
|
18484
|
+
const [focusArea, setFocusArea] = useState15("");
|
|
18485
|
+
const [isStarting, setIsStarting] = useState15(false);
|
|
18486
|
+
const [error, setError] = useState15(null);
|
|
18181
18487
|
const trackNames = Array.from(new Set(
|
|
18182
18488
|
assignments.filter((a) => a.testCase.track?.name).map((a) => a.testCase.track.name)
|
|
18183
18489
|
)).slice(0, 6);
|
|
18184
18490
|
const handleStart = async () => {
|
|
18185
18491
|
if (isStarting) return;
|
|
18492
|
+
setError(null);
|
|
18186
18493
|
Keyboard2.dismiss();
|
|
18187
18494
|
setIsStarting(true);
|
|
18188
18495
|
try {
|
|
@@ -18191,13 +18498,17 @@ function SessionStartScreen({ nav }) {
|
|
|
18191
18498
|
});
|
|
18192
18499
|
if (result.success) {
|
|
18193
18500
|
nav.replace({ name: "SESSION_ACTIVE" });
|
|
18501
|
+
} else {
|
|
18502
|
+
const msg = result.error || "Failed to start session. Please try again.";
|
|
18503
|
+
console.warn("BugBear: Session start failed:", msg);
|
|
18504
|
+
setError(msg);
|
|
18194
18505
|
}
|
|
18195
18506
|
} finally {
|
|
18196
18507
|
setIsStarting(false);
|
|
18197
18508
|
}
|
|
18198
18509
|
};
|
|
18199
18510
|
return /* @__PURE__ */ React18.createElement(View18, null, /* @__PURE__ */ React18.createElement(View18, { style: styles5.header }, /* @__PURE__ */ React18.createElement(Text16, { style: styles5.headerIcon }, "\u{1F50D}"), /* @__PURE__ */ React18.createElement(Text16, { style: styles5.headerDesc }, "Start an exploratory QA session. Log findings as you go \u2014 bugs, concerns, suggestions, or questions.")), /* @__PURE__ */ React18.createElement(View18, { style: styles5.inputSection }, /* @__PURE__ */ React18.createElement(Text16, { style: styles5.label }, "What are you testing?"), /* @__PURE__ */ React18.createElement(
|
|
18200
|
-
|
|
18511
|
+
TextInput10,
|
|
18201
18512
|
{
|
|
18202
18513
|
value: focusArea,
|
|
18203
18514
|
onChangeText: setFocusArea,
|
|
@@ -18216,7 +18527,7 @@ function SessionStartScreen({ nav }) {
|
|
|
18216
18527
|
activeOpacity: 0.7
|
|
18217
18528
|
},
|
|
18218
18529
|
/* @__PURE__ */ React18.createElement(Text16, { style: [styles5.chipText, focusArea === name && styles5.chipTextActive] }, name)
|
|
18219
|
-
)))), /* @__PURE__ */ React18.createElement(
|
|
18530
|
+
)))), error && /* @__PURE__ */ React18.createElement(View18, { style: styles5.errorBox }, /* @__PURE__ */ React18.createElement(Text16, { style: styles5.errorText }, error)), /* @__PURE__ */ React18.createElement(
|
|
18220
18531
|
TouchableOpacity15,
|
|
18221
18532
|
{
|
|
18222
18533
|
style: [styles5.startBtn, isStarting && styles5.startBtnDisabled],
|
|
@@ -18295,6 +18606,20 @@ function createStyles13() {
|
|
|
18295
18606
|
chipTextActive: {
|
|
18296
18607
|
color: colors.blueLight
|
|
18297
18608
|
},
|
|
18609
|
+
errorBox: {
|
|
18610
|
+
marginBottom: 12,
|
|
18611
|
+
paddingVertical: 10,
|
|
18612
|
+
paddingHorizontal: 12,
|
|
18613
|
+
backgroundColor: "rgba(239, 68, 68, 0.1)",
|
|
18614
|
+
borderWidth: 1,
|
|
18615
|
+
borderColor: "rgba(239, 68, 68, 0.3)",
|
|
18616
|
+
borderRadius: 8
|
|
18617
|
+
},
|
|
18618
|
+
errorText: {
|
|
18619
|
+
fontSize: 13,
|
|
18620
|
+
color: "#f87171",
|
|
18621
|
+
lineHeight: 18
|
|
18622
|
+
},
|
|
18298
18623
|
startBtn: {
|
|
18299
18624
|
paddingVertical: 14,
|
|
18300
18625
|
backgroundColor: colors.blueSurface,
|
|
@@ -18315,7 +18640,7 @@ function createStyles13() {
|
|
|
18315
18640
|
}
|
|
18316
18641
|
|
|
18317
18642
|
// src/widget/screens/SessionActiveScreen.tsx
|
|
18318
|
-
import React19, { useState as
|
|
18643
|
+
import React19, { useState as useState16, useEffect as useEffect11, useRef as useRef5, useMemo as useMemo14 } from "react";
|
|
18319
18644
|
import { View as View19, Text as Text17, TouchableOpacity as TouchableOpacity16, StyleSheet as StyleSheet19 } from "react-native";
|
|
18320
18645
|
function SessionActiveScreen({ nav }) {
|
|
18321
18646
|
const { activeSession, sessionFindings, endSession, refreshSession, widgetColorScheme } = useBugBear();
|
|
@@ -18326,8 +18651,8 @@ function SessionActiveScreen({ nav }) {
|
|
|
18326
18651
|
suggestion: { icon: "\u{1F4A1}", label: "Suggestion", color: colors.blue },
|
|
18327
18652
|
question: { icon: "\u2753", label: "Question", color: colors.violet }
|
|
18328
18653
|
}), [widgetColorScheme]);
|
|
18329
|
-
const [isEnding, setIsEnding] =
|
|
18330
|
-
const [elapsed, setElapsed] =
|
|
18654
|
+
const [isEnding, setIsEnding] = useState16(false);
|
|
18655
|
+
const [elapsed, setElapsed] = useState16(0);
|
|
18331
18656
|
const timerRef = useRef5(null);
|
|
18332
18657
|
useEffect11(() => {
|
|
18333
18658
|
refreshSession();
|
|
@@ -18552,8 +18877,8 @@ function createStyles14() {
|
|
|
18552
18877
|
}
|
|
18553
18878
|
|
|
18554
18879
|
// src/widget/screens/SessionFindingScreen.tsx
|
|
18555
|
-
import React20, { useState as
|
|
18556
|
-
import { View as View20, Text as Text18, TextInput as
|
|
18880
|
+
import React20, { useState as useState17, useMemo as useMemo15 } from "react";
|
|
18881
|
+
import { View as View20, Text as Text18, TextInput as TextInput11, TouchableOpacity as TouchableOpacity17, StyleSheet as StyleSheet20, Keyboard as Keyboard3 } from "react-native";
|
|
18557
18882
|
var FINDING_TYPES = [
|
|
18558
18883
|
{ value: "bug", icon: "\u{1F41B}", label: "Bug" },
|
|
18559
18884
|
{ value: "concern", icon: "\u26A0\uFE0F", label: "Concern" },
|
|
@@ -18570,11 +18895,11 @@ function SessionFindingScreen({ nav }) {
|
|
|
18570
18895
|
{ value: "low", label: "Low", color: colors.textMuted },
|
|
18571
18896
|
{ value: "observation", label: "Note", color: colors.textDim }
|
|
18572
18897
|
], [widgetColorScheme]);
|
|
18573
|
-
const [type, setType] =
|
|
18574
|
-
const [severity, setSeverity] =
|
|
18575
|
-
const [title, setTitle] =
|
|
18576
|
-
const [description, setDescription] =
|
|
18577
|
-
const [isSubmitting, setIsSubmitting] =
|
|
18898
|
+
const [type, setType] = useState17("bug");
|
|
18899
|
+
const [severity, setSeverity] = useState17("medium");
|
|
18900
|
+
const [title, setTitle] = useState17("");
|
|
18901
|
+
const [description, setDescription] = useState17("");
|
|
18902
|
+
const [isSubmitting, setIsSubmitting] = useState17(false);
|
|
18578
18903
|
const handleSubmit = async () => {
|
|
18579
18904
|
if (!title.trim() || isSubmitting) return;
|
|
18580
18905
|
Keyboard3.dismiss();
|
|
@@ -18617,7 +18942,7 @@ function SessionFindingScreen({ nav }) {
|
|
|
18617
18942
|
},
|
|
18618
18943
|
/* @__PURE__ */ React20.createElement(Text18, { style: [styles5.severityText, severity === s2.value && { color: s2.color }] }, s2.label)
|
|
18619
18944
|
)))), /* @__PURE__ */ React20.createElement(View20, { style: styles5.inputSection }, /* @__PURE__ */ React20.createElement(
|
|
18620
|
-
|
|
18945
|
+
TextInput11,
|
|
18621
18946
|
{
|
|
18622
18947
|
value: title,
|
|
18623
18948
|
onChangeText: setTitle,
|
|
@@ -18627,7 +18952,7 @@ function SessionFindingScreen({ nav }) {
|
|
|
18627
18952
|
returnKeyType: "next"
|
|
18628
18953
|
}
|
|
18629
18954
|
)), /* @__PURE__ */ React20.createElement(View20, { style: styles5.inputSection }, /* @__PURE__ */ React20.createElement(
|
|
18630
|
-
|
|
18955
|
+
TextInput11,
|
|
18631
18956
|
{
|
|
18632
18957
|
value: description,
|
|
18633
18958
|
onChangeText: setDescription,
|
|
@@ -18772,7 +19097,7 @@ function BugBearButton({
|
|
|
18772
19097
|
}) {
|
|
18773
19098
|
const { shouldShowWidget, testerInfo, isLoading, unreadCount, assignments, widgetMode, widgetColorScheme } = useBugBear();
|
|
18774
19099
|
const { currentScreen, canGoBack, push, pop, replace, reset } = useNavigation();
|
|
18775
|
-
const [modalVisible, setModalVisible] =
|
|
19100
|
+
const [modalVisible, setModalVisible] = useState18(false);
|
|
18776
19101
|
const styles5 = useMemo16(() => createStyles16(), [widgetColorScheme]);
|
|
18777
19102
|
const screenCaptureRef = useRef6(null);
|
|
18778
19103
|
const openModal = () => {
|
|
@@ -18980,7 +19305,7 @@ function BugBearButton({
|
|
|
18980
19305
|
style: styles5.modalOverlay
|
|
18981
19306
|
},
|
|
18982
19307
|
/* @__PURE__ */ React21.createElement(View21, { style: styles5.modalContainer }, /* @__PURE__ */ React21.createElement(View21, { style: styles5.header }, /* @__PURE__ */ React21.createElement(View21, { style: styles5.headerLeft }, canGoBack ? /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: () => nav.pop(), style: styles5.backButton }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.backText }, "\u2190 Back")) : /* @__PURE__ */ React21.createElement(View21, { style: styles5.headerTitleRow }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React21.createElement(Text19, { style: styles5.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React21.createElement(View21, { style: styles5.headerActions }, currentScreen.name !== "HOME" && /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: () => nav.reset(), style: styles5.homeButton }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.homeIcon }, "\u2302")), /* @__PURE__ */ React21.createElement(TouchableOpacity18, { onPress: handleClose, style: styles5.closeButton }, /* @__PURE__ */ React21.createElement(Text19, { style: styles5.closeText }, "\u2715")))), /* @__PURE__ */ React21.createElement(
|
|
18983
|
-
|
|
19308
|
+
ScrollView4,
|
|
18984
19309
|
{
|
|
18985
19310
|
style: styles5.content,
|
|
18986
19311
|
contentContainerStyle: styles5.contentContainer,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbearai/react-native",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "BugBear React Native components for mobile apps",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@bbearai/core": "^0.
|
|
58
|
+
"@bbearai/core": "^0.8.0",
|
|
59
59
|
"@eslint/js": "^9.39.2",
|
|
60
60
|
"@testing-library/jest-dom": "^6.9.1",
|
|
61
61
|
"@testing-library/react": "^16.3.2",
|