@bbearai/react-native 0.8.2 → 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.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +685 -36
- package/dist/index.mjs +738 -89
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -12197,10 +12197,14 @@ var BugBearClient = class {
|
|
|
12197
12197
|
this.navigationHistory = [];
|
|
12198
12198
|
this.reportSubmitInFlight = false;
|
|
12199
12199
|
this._queue = null;
|
|
12200
|
+
this._sessionStorage = new LocalStorageAdapter();
|
|
12200
12201
|
this.realtimeChannels = [];
|
|
12201
12202
|
this.monitor = null;
|
|
12202
12203
|
this.initialized = false;
|
|
12203
12204
|
this.initError = null;
|
|
12205
|
+
this._presenceSessionId = null;
|
|
12206
|
+
this._presenceInterval = null;
|
|
12207
|
+
this._presencePaused = false;
|
|
12204
12208
|
this.config = config;
|
|
12205
12209
|
if (config.apiKey) {
|
|
12206
12210
|
this.pendingInit = this.resolveFromApiKey(config.apiKey);
|
|
@@ -12274,6 +12278,10 @@ var BugBearClient = class {
|
|
|
12274
12278
|
this.initOfflineQueue();
|
|
12275
12279
|
this.initMonitoring();
|
|
12276
12280
|
}
|
|
12281
|
+
/** Cache key scoped to the active project. */
|
|
12282
|
+
get sessionCacheKey() {
|
|
12283
|
+
return `bugbear_session_${this.config.projectId ?? "unknown"}`;
|
|
12284
|
+
}
|
|
12277
12285
|
/** Initialize offline queue if configured. Shared by both init paths. */
|
|
12278
12286
|
initOfflineQueue() {
|
|
12279
12287
|
if (this.config.offlineQueue?.enabled) {
|
|
@@ -12337,6 +12345,26 @@ var BugBearClient = class {
|
|
|
12337
12345
|
await this.pendingInit;
|
|
12338
12346
|
if (this.initError) throw this.initError;
|
|
12339
12347
|
}
|
|
12348
|
+
/**
|
|
12349
|
+
* Fire-and-forget call to a dashboard notification endpoint.
|
|
12350
|
+
* Only works when apiKey is configured (needed for API auth).
|
|
12351
|
+
* Failures are silently ignored — notifications are best-effort.
|
|
12352
|
+
*/
|
|
12353
|
+
async notifyDashboard(path, body) {
|
|
12354
|
+
if (!this.config.apiKey) return;
|
|
12355
|
+
try {
|
|
12356
|
+
const baseUrl = (this.config.apiBaseUrl || DEFAULT_API_BASE_URL).replace(/\/$/, "");
|
|
12357
|
+
await fetch(`${baseUrl}/api/v1/notifications/${path}`, {
|
|
12358
|
+
method: "POST",
|
|
12359
|
+
headers: {
|
|
12360
|
+
"Content-Type": "application/json",
|
|
12361
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
12362
|
+
},
|
|
12363
|
+
body: JSON.stringify(body)
|
|
12364
|
+
});
|
|
12365
|
+
} catch {
|
|
12366
|
+
}
|
|
12367
|
+
}
|
|
12340
12368
|
// ── Offline Queue ─────────────────────────────────────────
|
|
12341
12369
|
/**
|
|
12342
12370
|
* Access the offline queue (if enabled).
|
|
@@ -12363,6 +12391,48 @@ var BugBearClient = class {
|
|
|
12363
12391
|
}
|
|
12364
12392
|
await this._queue.load();
|
|
12365
12393
|
}
|
|
12394
|
+
// ── Session Cache ──────────────────────────────────────────
|
|
12395
|
+
/**
|
|
12396
|
+
* Swap the session cache storage adapter (for React Native — pass AsyncStorage).
|
|
12397
|
+
* Must be called before getCachedSession() for persistence across app kills.
|
|
12398
|
+
* Web callers don't need this — LocalStorageAdapter is the default.
|
|
12399
|
+
*/
|
|
12400
|
+
setSessionStorage(adapter) {
|
|
12401
|
+
this._sessionStorage = adapter;
|
|
12402
|
+
}
|
|
12403
|
+
/**
|
|
12404
|
+
* Cache the active QA session locally for instant restore on app restart.
|
|
12405
|
+
* Pass null to clear the cache (e.g. after ending a session).
|
|
12406
|
+
*/
|
|
12407
|
+
async cacheSession(session) {
|
|
12408
|
+
try {
|
|
12409
|
+
if (session) {
|
|
12410
|
+
await this._sessionStorage.setItem(this.sessionCacheKey, JSON.stringify(session));
|
|
12411
|
+
} else {
|
|
12412
|
+
await this._sessionStorage.removeItem(this.sessionCacheKey);
|
|
12413
|
+
}
|
|
12414
|
+
} catch {
|
|
12415
|
+
}
|
|
12416
|
+
}
|
|
12417
|
+
/**
|
|
12418
|
+
* Retrieve the cached QA session. Returns null if no cache, if stale (>24h),
|
|
12419
|
+
* or if parsing fails. The DB fetch in initializeBugBear() is the source of truth.
|
|
12420
|
+
*/
|
|
12421
|
+
async getCachedSession() {
|
|
12422
|
+
try {
|
|
12423
|
+
const raw = await this._sessionStorage.getItem(this.sessionCacheKey);
|
|
12424
|
+
if (!raw) return null;
|
|
12425
|
+
const session = JSON.parse(raw);
|
|
12426
|
+
const age = Date.now() - new Date(session.startedAt).getTime();
|
|
12427
|
+
if (age > 24 * 60 * 60 * 1e3) {
|
|
12428
|
+
await this._sessionStorage.removeItem(this.sessionCacheKey);
|
|
12429
|
+
return null;
|
|
12430
|
+
}
|
|
12431
|
+
return session;
|
|
12432
|
+
} catch {
|
|
12433
|
+
return null;
|
|
12434
|
+
}
|
|
12435
|
+
}
|
|
12366
12436
|
registerQueueHandlers() {
|
|
12367
12437
|
if (!this._queue) return;
|
|
12368
12438
|
this._queue.registerHandler("report", async (payload) => {
|
|
@@ -12380,6 +12450,11 @@ var BugBearClient = class {
|
|
|
12380
12450
|
if (error) return { success: false, error: error.message };
|
|
12381
12451
|
return { success: true };
|
|
12382
12452
|
});
|
|
12453
|
+
this._queue.registerHandler("email_capture", async (payload) => {
|
|
12454
|
+
const { error } = await this.supabase.from("email_captures").insert(payload).select("id").single();
|
|
12455
|
+
if (error) return { success: false, error: error.message };
|
|
12456
|
+
return { success: true };
|
|
12457
|
+
});
|
|
12383
12458
|
}
|
|
12384
12459
|
// ── Realtime Subscriptions ─────────────────────────────────
|
|
12385
12460
|
/** Whether realtime is enabled in config. */
|
|
@@ -12567,6 +12642,8 @@ var BugBearClient = class {
|
|
|
12567
12642
|
if (this.config.onReportSubmitted) {
|
|
12568
12643
|
this.config.onReportSubmitted(report);
|
|
12569
12644
|
}
|
|
12645
|
+
this.notifyDashboard("report", { reportId: data.id }).catch(() => {
|
|
12646
|
+
});
|
|
12570
12647
|
return { success: true, reportId: data.id };
|
|
12571
12648
|
} catch (err) {
|
|
12572
12649
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -12579,6 +12656,44 @@ var BugBearClient = class {
|
|
|
12579
12656
|
this.reportSubmitInFlight = false;
|
|
12580
12657
|
}
|
|
12581
12658
|
}
|
|
12659
|
+
/**
|
|
12660
|
+
* Capture an email for QA testing.
|
|
12661
|
+
* Called by the email interceptor — not typically called directly.
|
|
12662
|
+
*/
|
|
12663
|
+
async captureEmail(payload) {
|
|
12664
|
+
try {
|
|
12665
|
+
await this.ready();
|
|
12666
|
+
if (!payload.subject || !payload.to || payload.to.length === 0) {
|
|
12667
|
+
return { success: false, error: "subject and to are required" };
|
|
12668
|
+
}
|
|
12669
|
+
const record = {
|
|
12670
|
+
project_id: this.config.projectId,
|
|
12671
|
+
to_addresses: payload.to,
|
|
12672
|
+
from_address: payload.from || null,
|
|
12673
|
+
subject: payload.subject,
|
|
12674
|
+
html_content: payload.html || null,
|
|
12675
|
+
text_content: payload.text || null,
|
|
12676
|
+
template_id: payload.templateId || null,
|
|
12677
|
+
metadata: payload.metadata || {},
|
|
12678
|
+
capture_mode: payload.captureMode,
|
|
12679
|
+
was_delivered: payload.wasDelivered,
|
|
12680
|
+
delivery_status: payload.wasDelivered ? "sent" : "pending"
|
|
12681
|
+
};
|
|
12682
|
+
const { data, error } = await this.supabase.from("email_captures").insert(record).select("id").single();
|
|
12683
|
+
if (error) {
|
|
12684
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
12685
|
+
await this._queue.enqueue("email_capture", record);
|
|
12686
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
12687
|
+
}
|
|
12688
|
+
console.error("BugBear: Failed to capture email", error.message);
|
|
12689
|
+
return { success: false, error: error.message };
|
|
12690
|
+
}
|
|
12691
|
+
return { success: true, captureId: data.id };
|
|
12692
|
+
} catch (err) {
|
|
12693
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
12694
|
+
return { success: false, error: message };
|
|
12695
|
+
}
|
|
12696
|
+
}
|
|
12582
12697
|
/**
|
|
12583
12698
|
* Get assigned tests for current user
|
|
12584
12699
|
* First looks up the tester by email, then fetches their assignments
|
|
@@ -13200,6 +13315,32 @@ var BugBearClient = class {
|
|
|
13200
13315
|
return [];
|
|
13201
13316
|
}
|
|
13202
13317
|
}
|
|
13318
|
+
/**
|
|
13319
|
+
* Reopen a done issue that the tester believes isn't actually fixed.
|
|
13320
|
+
* Transitions the report from a done status back to 'confirmed'.
|
|
13321
|
+
*/
|
|
13322
|
+
async reopenReport(reportId, reason) {
|
|
13323
|
+
try {
|
|
13324
|
+
const testerInfo = await this.getTesterInfo();
|
|
13325
|
+
if (!testerInfo) return { success: false, error: "Not authenticated as tester" };
|
|
13326
|
+
const { data, error } = await this.supabase.rpc("reopen_report", {
|
|
13327
|
+
p_report_id: reportId,
|
|
13328
|
+
p_tester_id: testerInfo.id,
|
|
13329
|
+
p_reason: reason
|
|
13330
|
+
});
|
|
13331
|
+
if (error) {
|
|
13332
|
+
console.error("BugBear: Failed to reopen report", formatPgError(error));
|
|
13333
|
+
return { success: false, error: error.message };
|
|
13334
|
+
}
|
|
13335
|
+
if (!data?.success) {
|
|
13336
|
+
return { success: false, error: data?.error || "Failed to reopen report" };
|
|
13337
|
+
}
|
|
13338
|
+
return { success: true };
|
|
13339
|
+
} catch (err) {
|
|
13340
|
+
console.error("BugBear: Error reopening report", err);
|
|
13341
|
+
return { success: false, error: "Unexpected error" };
|
|
13342
|
+
}
|
|
13343
|
+
}
|
|
13203
13344
|
/**
|
|
13204
13345
|
* Basic email format validation (defense in depth)
|
|
13205
13346
|
*/
|
|
@@ -13709,6 +13850,7 @@ var BugBearClient = class {
|
|
|
13709
13850
|
lastMessageAt: row.last_message_at,
|
|
13710
13851
|
createdAt: row.created_at,
|
|
13711
13852
|
unreadCount: Number(row.unread_count) || 0,
|
|
13853
|
+
reporterName: row.reporter_name || void 0,
|
|
13712
13854
|
lastMessage: row.last_message_preview ? {
|
|
13713
13855
|
id: "",
|
|
13714
13856
|
threadId: row.thread_id,
|
|
@@ -13786,7 +13928,7 @@ var BugBearClient = class {
|
|
|
13786
13928
|
insertData.attachments = safeAttachments;
|
|
13787
13929
|
}
|
|
13788
13930
|
}
|
|
13789
|
-
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
13931
|
+
const { data: msgData, error } = await this.supabase.from("discussion_messages").insert(insertData).select("id").single();
|
|
13790
13932
|
if (error) {
|
|
13791
13933
|
if (this._queue && isNetworkError(error.message)) {
|
|
13792
13934
|
await this._queue.enqueue("message", insertData);
|
|
@@ -13795,6 +13937,10 @@ var BugBearClient = class {
|
|
|
13795
13937
|
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
13796
13938
|
return false;
|
|
13797
13939
|
}
|
|
13940
|
+
if (msgData?.id) {
|
|
13941
|
+
this.notifyDashboard("message", { threadId, messageId: msgData.id }).catch(() => {
|
|
13942
|
+
});
|
|
13943
|
+
}
|
|
13798
13944
|
await this.markThreadAsRead(threadId);
|
|
13799
13945
|
return true;
|
|
13800
13946
|
} catch (err) {
|
|
@@ -13920,6 +14066,7 @@ var BugBearClient = class {
|
|
|
13920
14066
|
if (!session) {
|
|
13921
14067
|
return { success: false, error: "Session created but could not be fetched" };
|
|
13922
14068
|
}
|
|
14069
|
+
await this.cacheSession(session);
|
|
13923
14070
|
return { success: true, session };
|
|
13924
14071
|
} catch (err) {
|
|
13925
14072
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -13943,6 +14090,7 @@ var BugBearClient = class {
|
|
|
13943
14090
|
return { success: false, error: error.message };
|
|
13944
14091
|
}
|
|
13945
14092
|
const session = this.transformSession(data);
|
|
14093
|
+
await this.cacheSession(null);
|
|
13946
14094
|
return { success: true, session };
|
|
13947
14095
|
} catch (err) {
|
|
13948
14096
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -13958,8 +14106,13 @@ var BugBearClient = class {
|
|
|
13958
14106
|
const testerInfo = await this.getTesterInfo();
|
|
13959
14107
|
if (!testerInfo) return null;
|
|
13960
14108
|
const { data, error } = await this.supabase.from("qa_sessions").select("*").eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).eq("status", "active").order("started_at", { ascending: false }).limit(1).maybeSingle();
|
|
13961
|
-
if (error || !data)
|
|
13962
|
-
|
|
14109
|
+
if (error || !data) {
|
|
14110
|
+
await this.cacheSession(null);
|
|
14111
|
+
return null;
|
|
14112
|
+
}
|
|
14113
|
+
const session = this.transformSession(data);
|
|
14114
|
+
await this.cacheSession(session);
|
|
14115
|
+
return session;
|
|
13963
14116
|
} catch (err) {
|
|
13964
14117
|
console.error("BugBear: Error fetching active session", err);
|
|
13965
14118
|
return null;
|
|
@@ -14137,6 +14290,93 @@ var BugBearClient = class {
|
|
|
14137
14290
|
updatedAt: data.updated_at
|
|
14138
14291
|
};
|
|
14139
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
|
+
}
|
|
14140
14380
|
};
|
|
14141
14381
|
function createBugBear(config) {
|
|
14142
14382
|
return new BugBearClient(config);
|
|
@@ -14271,6 +14511,9 @@ function setActiveColors(palette) {
|
|
|
14271
14511
|
colors = palette;
|
|
14272
14512
|
shared = createSharedStyles();
|
|
14273
14513
|
}
|
|
14514
|
+
function withAlpha(hex, alpha) {
|
|
14515
|
+
return hex + Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
14516
|
+
}
|
|
14274
14517
|
function createSharedStyles() {
|
|
14275
14518
|
return StyleSheet.create({
|
|
14276
14519
|
card: {
|
|
@@ -14477,6 +14720,7 @@ var BugBearContext = createContext({
|
|
|
14477
14720
|
issueCounts: { open: 0, done: 0, reopened: 0 },
|
|
14478
14721
|
refreshIssueCounts: async () => {
|
|
14479
14722
|
},
|
|
14723
|
+
reopenReport: async () => ({ success: false }),
|
|
14480
14724
|
queuedCount: 0,
|
|
14481
14725
|
dashboardUrl: void 0,
|
|
14482
14726
|
onError: void 0
|
|
@@ -14618,6 +14862,14 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14618
14862
|
const counts = await client.getIssueCounts();
|
|
14619
14863
|
setIssueCounts(counts);
|
|
14620
14864
|
}, [client]);
|
|
14865
|
+
const reopenReport = useCallback(async (reportId, reason) => {
|
|
14866
|
+
if (!client) return { success: false, error: "Client not initialized" };
|
|
14867
|
+
const result = await client.reopenReport(reportId, reason);
|
|
14868
|
+
if (result.success) {
|
|
14869
|
+
await refreshIssueCounts();
|
|
14870
|
+
}
|
|
14871
|
+
return result;
|
|
14872
|
+
}, [client, refreshIssueCounts]);
|
|
14621
14873
|
const initializeBugBear = useCallback(async (bugBearClient) => {
|
|
14622
14874
|
setIsLoading(true);
|
|
14623
14875
|
try {
|
|
@@ -14703,6 +14955,10 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14703
14955
|
setClient(newClient);
|
|
14704
14956
|
(async () => {
|
|
14705
14957
|
try {
|
|
14958
|
+
const cachedSession = await newClient.getCachedSession();
|
|
14959
|
+
if (cachedSession) {
|
|
14960
|
+
setActiveSession(cachedSession);
|
|
14961
|
+
}
|
|
14706
14962
|
await initializeBugBear(newClient);
|
|
14707
14963
|
if (newClient.monitor && config.monitoring) {
|
|
14708
14964
|
const getCurrentRoute = () => contextCapture.getCurrentRoute();
|
|
@@ -14760,6 +15016,28 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14760
15016
|
}
|
|
14761
15017
|
return () => subscription.remove();
|
|
14762
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]);
|
|
14763
15041
|
useEffect(() => {
|
|
14764
15042
|
if (!client || !isTester) return;
|
|
14765
15043
|
if (widgetMode === "qa" && !isQAEnabled) return;
|
|
@@ -14837,6 +15115,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14837
15115
|
// Issue tracking
|
|
14838
15116
|
issueCounts,
|
|
14839
15117
|
refreshIssueCounts,
|
|
15118
|
+
reopenReport,
|
|
14840
15119
|
queuedCount,
|
|
14841
15120
|
dashboardUrl: config.dashboardUrl,
|
|
14842
15121
|
onError: config.onError
|
|
@@ -14854,21 +15133,21 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14854
15133
|
}
|
|
14855
15134
|
|
|
14856
15135
|
// src/BugBearButton.tsx
|
|
14857
|
-
import React21, { useState as
|
|
15136
|
+
import React21, { useState as useState18, useRef as useRef6, useMemo as useMemo16 } from "react";
|
|
14858
15137
|
import {
|
|
14859
15138
|
View as View21,
|
|
14860
15139
|
Text as Text19,
|
|
14861
15140
|
Image as Image4,
|
|
14862
15141
|
TouchableOpacity as TouchableOpacity18,
|
|
14863
15142
|
Modal as Modal3,
|
|
14864
|
-
ScrollView as
|
|
15143
|
+
ScrollView as ScrollView4,
|
|
14865
15144
|
StyleSheet as StyleSheet21,
|
|
14866
15145
|
Dimensions as Dimensions2,
|
|
14867
15146
|
KeyboardAvoidingView,
|
|
14868
15147
|
Platform as Platform5,
|
|
14869
15148
|
PanResponder,
|
|
14870
15149
|
Animated as Animated2,
|
|
14871
|
-
ActivityIndicator as
|
|
15150
|
+
ActivityIndicator as ActivityIndicator3,
|
|
14872
15151
|
Keyboard as Keyboard4
|
|
14873
15152
|
} from "react-native";
|
|
14874
15153
|
|
|
@@ -15583,6 +15862,17 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
15583
15862
|
setShowSteps(true);
|
|
15584
15863
|
setShowDetails(false);
|
|
15585
15864
|
}, [displayedAssignment?.id]);
|
|
15865
|
+
useEffect4(() => {
|
|
15866
|
+
if (!client || !displayedAssignment || displayedAssignment.status !== "pending") return;
|
|
15867
|
+
let cancelled = false;
|
|
15868
|
+
(async () => {
|
|
15869
|
+
await client.updateAssignmentStatus(displayedAssignment.id, "in_progress");
|
|
15870
|
+
if (!cancelled) await refreshAssignments();
|
|
15871
|
+
})();
|
|
15872
|
+
return () => {
|
|
15873
|
+
cancelled = true;
|
|
15874
|
+
};
|
|
15875
|
+
}, [client, displayedAssignment?.id, displayedAssignment?.status, refreshAssignments]);
|
|
15586
15876
|
useEffect4(() => {
|
|
15587
15877
|
const active = displayedAssignment?.status === "in_progress" ? displayedAssignment : null;
|
|
15588
15878
|
if (!active?.startedAt) {
|
|
@@ -15633,17 +15923,6 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
15633
15923
|
setIsSubmitting(false);
|
|
15634
15924
|
}
|
|
15635
15925
|
}, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
|
|
15636
|
-
const handleStart = useCallback2(async () => {
|
|
15637
|
-
if (!client || !displayedAssignment || isSubmitting) return;
|
|
15638
|
-
Keyboard.dismiss();
|
|
15639
|
-
setIsSubmitting(true);
|
|
15640
|
-
try {
|
|
15641
|
-
await client.updateAssignmentStatus(displayedAssignment.id, "in_progress");
|
|
15642
|
-
await refreshAssignments();
|
|
15643
|
-
} finally {
|
|
15644
|
-
setIsSubmitting(false);
|
|
15645
|
-
}
|
|
15646
|
-
}, [client, displayedAssignment, refreshAssignments, isSubmitting]);
|
|
15647
15926
|
const handleReopen = useCallback2(async () => {
|
|
15648
15927
|
if (!client || !displayedAssignment || isSubmitting) return;
|
|
15649
15928
|
Keyboard.dismiss();
|
|
@@ -15789,15 +16068,7 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
15789
16068
|
disabled: isSubmitting
|
|
15790
16069
|
},
|
|
15791
16070
|
/* @__PURE__ */ React4.createElement(Text2, { style: styles5.passBtnText }, "Change to Pass")
|
|
15792
|
-
))) : displayedAssignment.
|
|
15793
|
-
TouchableOpacity2,
|
|
15794
|
-
{
|
|
15795
|
-
style: [styles5.startBtn, isSubmitting && { opacity: 0.5 }],
|
|
15796
|
-
onPress: handleStart,
|
|
15797
|
-
disabled: isSubmitting
|
|
15798
|
-
},
|
|
15799
|
-
/* @__PURE__ */ React4.createElement(Text2, { style: styles5.startBtnText }, isSubmitting ? "Starting..." : displayedAssignment.isVerification ? "\u{1F50D} Start Verification" : "\u25B6 Start Test")
|
|
15800
|
-
) : /* @__PURE__ */ React4.createElement(View4, { style: styles5.actionButtons }, /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.failBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Reporting..." : "Failing..." : displayedAssignment.isVerification ? "\u2717 Still Broken" : "Fail")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.skipBtnText }, "Skip")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.passBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Verifying..." : "Passing..." : displayedAssignment.isVerification ? "\u2713 Fix Verified" : "Pass"))), /* @__PURE__ */ React4.createElement(Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ React4.createElement(View4, { style: styles5.modalOverlay }, /* @__PURE__ */ React4.createElement(View4, { style: styles5.modalContent }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.modalTitle }, "Skip this test?"), /* @__PURE__ */ React4.createElement(Text2, { style: styles5.modalSubtitle }, "Select a reason:"), [
|
|
16071
|
+
))) : /* @__PURE__ */ React4.createElement(View4, { style: styles5.actionButtons }, /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.failBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Reporting..." : "Failing..." : displayedAssignment.isVerification ? "\u2717 Still Broken" : "Fail")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.skipBtnText }, "Skip")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles5.actionBtn, styles5.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.passBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Verifying..." : "Passing..." : displayedAssignment.isVerification ? "\u2713 Fix Verified" : "Pass"))), /* @__PURE__ */ React4.createElement(Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ React4.createElement(View4, { style: styles5.modalOverlay }, /* @__PURE__ */ React4.createElement(View4, { style: styles5.modalContent }, /* @__PURE__ */ React4.createElement(Text2, { style: styles5.modalTitle }, "Skip this test?"), /* @__PURE__ */ React4.createElement(Text2, { style: styles5.modalSubtitle }, "Select a reason:"), [
|
|
15801
16072
|
{ reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
|
|
15802
16073
|
{ reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
|
|
15803
16074
|
{ reason: "dependency", label: "\u{1F517} Needs another test first" },
|
|
@@ -15911,9 +16182,6 @@ function createStyles2() {
|
|
|
15911
16182
|
completedLabel: { fontSize: 13, fontWeight: "600", color: colors.textSecondary },
|
|
15912
16183
|
reopenBtn: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.blue },
|
|
15913
16184
|
reopenBtnText: { fontSize: 14, fontWeight: "600", color: colors.blue },
|
|
15914
|
-
// Start Test button
|
|
15915
|
-
startBtn: { paddingVertical: 16, borderRadius: 12, alignItems: "center", backgroundColor: colors.blueSurface, borderWidth: 1, borderColor: colors.blueAccent, marginTop: 8 },
|
|
15916
|
-
startBtnText: { fontSize: 16, fontWeight: "600", color: colors.blueText },
|
|
15917
16185
|
// Action buttons
|
|
15918
16186
|
actionButtons: { flexDirection: "row", gap: 10, marginTop: 8 },
|
|
15919
16187
|
actionBtn: { flex: 1, paddingVertical: 14, borderRadius: 12, alignItems: "center" },
|
|
@@ -16782,6 +17050,8 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16782
17050
|
const [severity, setSeverity] = useState8("medium");
|
|
16783
17051
|
const [category, setCategory] = useState8(null);
|
|
16784
17052
|
const [description, setDescription] = useState8("");
|
|
17053
|
+
const [myIssues, setMyIssues] = useState8([]);
|
|
17054
|
+
const [similarReports, setSimilarReports] = useState8([]);
|
|
16785
17055
|
const [affectedScreen, setAffectedScreen] = useState8("");
|
|
16786
17056
|
const [submitting, setSubmitting] = useState8(false);
|
|
16787
17057
|
const [error, setError] = useState8(null);
|
|
@@ -16797,6 +17067,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16797
17067
|
}, [autoCaptureUri, onAutoCaptureConsumed]);
|
|
16798
17068
|
const isRetestFailure = prefill?.type === "test_fail";
|
|
16799
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]);
|
|
16800
17105
|
useEffect6(() => {
|
|
16801
17106
|
if (reportType === "feedback" || reportType === "suggestion") {
|
|
16802
17107
|
setCategory("other");
|
|
@@ -16871,7 +17176,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16871
17176
|
numberOfLines: 4,
|
|
16872
17177
|
textAlignVertical: "top"
|
|
16873
17178
|
}
|
|
16874
|
-
)
|
|
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(
|
|
16875
17214
|
ImagePickerButtons,
|
|
16876
17215
|
{
|
|
16877
17216
|
images: images.images,
|
|
@@ -16927,7 +17266,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16927
17266
|
numberOfLines: 4,
|
|
16928
17267
|
textAlignVertical: "top"
|
|
16929
17268
|
}
|
|
16930
|
-
)
|
|
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(
|
|
16931
17304
|
TextInput4,
|
|
16932
17305
|
{
|
|
16933
17306
|
style: styles5.screenInput,
|
|
@@ -17003,11 +17376,24 @@ function createStyles6() {
|
|
|
17003
17376
|
}
|
|
17004
17377
|
|
|
17005
17378
|
// src/widget/screens/MessageListScreen.tsx
|
|
17006
|
-
import React12, { useMemo as useMemo7 } from "react";
|
|
17007
|
-
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";
|
|
17008
17381
|
function MessageListScreen({ nav }) {
|
|
17009
17382
|
const { threads, unreadCount, refreshThreads, dashboardUrl, isLoading, widgetColorScheme } = useBugBear();
|
|
17010
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
|
+
];
|
|
17011
17397
|
if (isLoading) return /* @__PURE__ */ React12.createElement(MessageListScreenSkeleton, null);
|
|
17012
17398
|
return /* @__PURE__ */ React12.createElement(View12, null, /* @__PURE__ */ React12.createElement(
|
|
17013
17399
|
TouchableOpacity9,
|
|
@@ -17016,14 +17402,54 @@ function MessageListScreen({ nav }) {
|
|
|
17016
17402
|
onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
|
|
17017
17403
|
},
|
|
17018
17404
|
/* @__PURE__ */ React12.createElement(Text10, { style: styles5.newMsgText }, "\u2709\uFE0F New Message")
|
|
17019
|
-
),
|
|
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(
|
|
17020
17446
|
TouchableOpacity9,
|
|
17021
17447
|
{
|
|
17022
17448
|
key: thread.id,
|
|
17023
17449
|
style: [styles5.threadItem, thread.unreadCount > 0 && styles5.threadItemUnread],
|
|
17024
17450
|
onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
|
|
17025
17451
|
},
|
|
17026
|
-
/* @__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))),
|
|
17027
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) }] }))
|
|
17028
17454
|
))), dashboardUrl && /* @__PURE__ */ React12.createElement(
|
|
17029
17455
|
TouchableOpacity9,
|
|
@@ -17033,7 +17459,7 @@ function MessageListScreen({ nav }) {
|
|
|
17033
17459
|
activeOpacity: 0.7
|
|
17034
17460
|
},
|
|
17035
17461
|
/* @__PURE__ */ React12.createElement(Text10, { style: styles5.dashboardLinkText }, "\u{1F310}", " View on Dashboard ", "\u2192")
|
|
17036
|
-
), /* @__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"))));
|
|
17037
17463
|
}
|
|
17038
17464
|
function createStyles7() {
|
|
17039
17465
|
return StyleSheet12.create({
|
|
@@ -17062,16 +17488,16 @@ function createStyles7() {
|
|
|
17062
17488
|
}
|
|
17063
17489
|
|
|
17064
17490
|
// src/widget/screens/ThreadDetailScreen.tsx
|
|
17065
|
-
import React13, { useState as
|
|
17491
|
+
import React13, { useState as useState10, useEffect as useEffect8, useMemo as useMemo8 } from "react";
|
|
17066
17492
|
import { View as View13, Text as Text11, TouchableOpacity as TouchableOpacity10, TextInput as TextInput5, StyleSheet as StyleSheet13, Image as Image2 } from "react-native";
|
|
17067
17493
|
function ThreadDetailScreen({ thread, nav }) {
|
|
17068
17494
|
const { getThreadMessages, sendMessage, markAsRead, uploadImage, widgetColorScheme } = useBugBear();
|
|
17069
17495
|
const styles5 = useMemo8(() => createStyles8(), [widgetColorScheme]);
|
|
17070
|
-
const [messages, setMessages] =
|
|
17071
|
-
const [loading, setLoading] =
|
|
17072
|
-
const [replyText, setReplyText] =
|
|
17073
|
-
const [sending, setSending] =
|
|
17074
|
-
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);
|
|
17075
17501
|
const replyImages = useImageAttachments(uploadImage, 3, "discussion-attachments");
|
|
17076
17502
|
useEffect8(() => {
|
|
17077
17503
|
let cancelled = false;
|
|
@@ -17183,14 +17609,14 @@ function createStyles8() {
|
|
|
17183
17609
|
}
|
|
17184
17610
|
|
|
17185
17611
|
// src/widget/screens/ComposeMessageScreen.tsx
|
|
17186
|
-
import React14, { useState as
|
|
17612
|
+
import React14, { useState as useState11, useMemo as useMemo9 } from "react";
|
|
17187
17613
|
import { View as View14, Text as Text12, TextInput as TextInput6, TouchableOpacity as TouchableOpacity11, StyleSheet as StyleSheet14 } from "react-native";
|
|
17188
17614
|
function ComposeMessageScreen({ nav }) {
|
|
17189
17615
|
const { createThread, uploadImage, widgetColorScheme } = useBugBear();
|
|
17190
17616
|
const styles5 = useMemo9(() => createStyles9(), [widgetColorScheme]);
|
|
17191
|
-
const [subject, setSubject] =
|
|
17192
|
-
const [message, setMessage] =
|
|
17193
|
-
const [sending, setSending] =
|
|
17617
|
+
const [subject, setSubject] = useState11("");
|
|
17618
|
+
const [message, setMessage] = useState11("");
|
|
17619
|
+
const [sending, setSending] = useState11(false);
|
|
17194
17620
|
const images = useImageAttachments(uploadImage, 3, "discussion-attachments");
|
|
17195
17621
|
const handleSend = async () => {
|
|
17196
17622
|
if (!subject.trim() || !message.trim() || sending || images.isUploading) return;
|
|
@@ -17260,19 +17686,19 @@ function createStyles9() {
|
|
|
17260
17686
|
}
|
|
17261
17687
|
|
|
17262
17688
|
// src/widget/screens/ProfileScreen.tsx
|
|
17263
|
-
import React15, { useState as
|
|
17689
|
+
import React15, { useState as useState12, useEffect as useEffect9, useMemo as useMemo10 } from "react";
|
|
17264
17690
|
import { View as View15, Text as Text13, TouchableOpacity as TouchableOpacity12, TextInput as TextInput7, StyleSheet as StyleSheet15 } from "react-native";
|
|
17265
17691
|
function ProfileScreen({ nav }) {
|
|
17266
17692
|
const { testerInfo, assignments, updateTesterProfile, refreshTesterInfo, widgetColorScheme } = useBugBear();
|
|
17267
17693
|
const styles5 = useMemo10(() => createStyles10(), [widgetColorScheme]);
|
|
17268
|
-
const [editing, setEditing] =
|
|
17269
|
-
const [name, setName] =
|
|
17270
|
-
const [additionalEmails, setAdditionalEmails] =
|
|
17271
|
-
const [newEmailInput, setNewEmailInput] =
|
|
17272
|
-
const [platforms, setPlatforms] =
|
|
17273
|
-
const [saving, setSaving] =
|
|
17274
|
-
const [saved, setSaved] =
|
|
17275
|
-
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);
|
|
17276
17702
|
const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
|
|
17277
17703
|
useEffect9(() => {
|
|
17278
17704
|
if (testerInfo) {
|
|
@@ -17391,8 +17817,8 @@ function createStyles10() {
|
|
|
17391
17817
|
}
|
|
17392
17818
|
|
|
17393
17819
|
// src/widget/screens/IssueListScreen.tsx
|
|
17394
|
-
import React16, { useState as
|
|
17395
|
-
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";
|
|
17396
17822
|
var CATEGORIES = ["open", "done", "reopened"];
|
|
17397
17823
|
var SEVERITY_ORDER2 = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
17398
17824
|
function IssueListScreen({ nav, category }) {
|
|
@@ -17409,12 +17835,22 @@ function IssueListScreen({ nav, category }) {
|
|
|
17409
17835
|
medium: colors.yellow,
|
|
17410
17836
|
low: colors.textMuted
|
|
17411
17837
|
}), [widgetColorScheme]);
|
|
17412
|
-
const [activeCategory, setActiveCategory] =
|
|
17413
|
-
const [issues, setIssues] =
|
|
17414
|
-
const [loading, setLoading] =
|
|
17415
|
-
const [counts, setCounts] =
|
|
17416
|
-
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("");
|
|
17417
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]);
|
|
17418
17854
|
useEffect10(() => {
|
|
17419
17855
|
if (!client) return;
|
|
17420
17856
|
client.getIssueCounts().then(setCounts).catch(() => {
|
|
@@ -17454,6 +17890,9 @@ function IssueListScreen({ nav, category }) {
|
|
|
17454
17890
|
}
|
|
17455
17891
|
return sorted;
|
|
17456
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;
|
|
17457
17896
|
return /* @__PURE__ */ React16.createElement(View16, null, /* @__PURE__ */ React16.createElement(View16, { style: styles5.tabBar }, CATEGORIES.map((cat) => {
|
|
17458
17897
|
const catConfig = CATEGORY_CONFIG[cat];
|
|
17459
17898
|
const isActive = activeCategory === cat;
|
|
@@ -17500,7 +17939,25 @@ function IssueListScreen({ nav, category }) {
|
|
|
17500
17939
|
styles5.sortBtnText,
|
|
17501
17940
|
sortMode === s2.key && styles5.sortBtnTextActive
|
|
17502
17941
|
] }, s2.label)
|
|
17503
|
-
))),
|
|
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(
|
|
17504
17961
|
TouchableOpacity13,
|
|
17505
17962
|
{
|
|
17506
17963
|
key: issue.id,
|
|
@@ -17665,11 +18122,17 @@ function createStyles11() {
|
|
|
17665
18122
|
}
|
|
17666
18123
|
|
|
17667
18124
|
// src/widget/screens/IssueDetailScreen.tsx
|
|
17668
|
-
import React17, { useMemo as useMemo12 } from "react";
|
|
17669
|
-
import { View as View17, Text as Text15, Image as Image3, StyleSheet as StyleSheet17, Linking as Linking4, TouchableOpacity as TouchableOpacity14 } from "react-native";
|
|
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";
|
|
18127
|
+
var DONE_STATUSES = ["verified", "resolved", "closed", "reviewed"];
|
|
17670
18128
|
function IssueDetailScreen({ nav, issue }) {
|
|
17671
|
-
const { dashboardUrl, widgetColorScheme } = useBugBear();
|
|
18129
|
+
const { dashboardUrl, widgetColorScheme, reopenReport } = useBugBear();
|
|
17672
18130
|
const styles5 = useMemo12(() => createStyles12(), [widgetColorScheme]);
|
|
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);
|
|
17673
18136
|
const STATUS_LABELS = useMemo12(() => ({
|
|
17674
18137
|
new: { label: "New", bg: colors.blueDark, color: colors.blueLight },
|
|
17675
18138
|
triaging: { label: "Triaging", bg: colors.blueDark, color: colors.blueLight },
|
|
@@ -17692,7 +18155,68 @@ function IssueDetailScreen({ nav, issue }) {
|
|
|
17692
18155
|
}), [widgetColorScheme]);
|
|
17693
18156
|
const statusConfig = STATUS_LABELS[issue.status] || { label: issue.status, bg: colors.card, color: colors.textSecondary };
|
|
17694
18157
|
const severityConfig = issue.severity ? SEVERITY_CONFIG[issue.severity] : null;
|
|
17695
|
-
|
|
18158
|
+
const isDone = DONE_STATUSES.includes(issue.status);
|
|
18159
|
+
const handleReopen = useCallback5(async () => {
|
|
18160
|
+
if (!reopenReason.trim()) return;
|
|
18161
|
+
setIsSubmitting(true);
|
|
18162
|
+
setReopenError(null);
|
|
18163
|
+
const result = await reopenReport(issue.id, reopenReason.trim());
|
|
18164
|
+
setIsSubmitting(false);
|
|
18165
|
+
if (result.success) {
|
|
18166
|
+
setWasReopened(true);
|
|
18167
|
+
setShowReopenForm(false);
|
|
18168
|
+
} else {
|
|
18169
|
+
setReopenError(result.error || "Failed to reopen");
|
|
18170
|
+
}
|
|
18171
|
+
}, [reopenReason, reopenReport, issue.id]);
|
|
18172
|
+
return /* @__PURE__ */ React17.createElement(View17, null, /* @__PURE__ */ React17.createElement(View17, { style: styles5.badgeRow }, /* @__PURE__ */ React17.createElement(View17, { style: [styles5.badge, { backgroundColor: wasReopened ? colors.yellowDark : statusConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles5.badgeText, { color: wasReopened ? colors.yellowLight : statusConfig.color }] }, wasReopened ? "Reopened" : statusConfig.label)), severityConfig && /* @__PURE__ */ React17.createElement(View17, { style: [styles5.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles5.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.title }, issue.title), issue.route && /* @__PURE__ */ React17.createElement(Text15, { style: styles5.route }, issue.route), issue.description && /* @__PURE__ */ React17.createElement(View17, { style: styles5.descriptionCard }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.descriptionText }, issue.description)), wasReopened && /* @__PURE__ */ React17.createElement(View17, { style: styles5.reopenedCard }, /* @__PURE__ */ React17.createElement(View17, { style: styles5.reopenedRow }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenedIcon }, "\u{1F504}"), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenedText }, "Issue reopened \u2014 your team has been notified"))), issue.verifiedByName && !wasReopened && /* @__PURE__ */ React17.createElement(View17, { style: styles5.verifiedCard }, /* @__PURE__ */ React17.createElement(View17, { style: styles5.verifiedHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.verifiedIcon }, "\u2705"), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.verifiedBody }, "Verified by ", issue.verifiedByName, issue.verifiedAt && ` on ${new Date(issue.verifiedAt).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" })}`)), issue.originalBugTitle && /* @__PURE__ */ React17.createElement(View17, { style: styles5.originalBugCard }, /* @__PURE__ */ React17.createElement(View17, { style: styles5.originalBugHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.originalBugTitleText }, "Original Bug")), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.originalBugBody }, "Retest of: ", issue.originalBugTitle)), isDone && !wasReopened && !showReopenForm && /* @__PURE__ */ React17.createElement(
|
|
18173
|
+
TouchableOpacity14,
|
|
18174
|
+
{
|
|
18175
|
+
style: styles5.reopenButton,
|
|
18176
|
+
onPress: () => setShowReopenForm(true),
|
|
18177
|
+
activeOpacity: 0.7
|
|
18178
|
+
},
|
|
18179
|
+
/* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenButtonText }, "\u{1F504}", " Not Fixed \u2014 Reopen Issue")
|
|
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(
|
|
18181
|
+
TextInput9,
|
|
18182
|
+
{
|
|
18183
|
+
value: reopenReason,
|
|
18184
|
+
onChangeText: setReopenReason,
|
|
18185
|
+
placeholder: "Describe what you're still seeing...",
|
|
18186
|
+
placeholderTextColor: colors.textMuted,
|
|
18187
|
+
style: styles5.reopenInput,
|
|
18188
|
+
multiline: true,
|
|
18189
|
+
autoFocus: true
|
|
18190
|
+
}
|
|
18191
|
+
), reopenError && /* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenErrorText }, reopenError), /* @__PURE__ */ React17.createElement(View17, { style: styles5.reopenActions }, /* @__PURE__ */ React17.createElement(
|
|
18192
|
+
TouchableOpacity14,
|
|
18193
|
+
{
|
|
18194
|
+
style: [
|
|
18195
|
+
styles5.reopenSubmitButton,
|
|
18196
|
+
(!reopenReason.trim() || isSubmitting) && styles5.reopenSubmitDisabled
|
|
18197
|
+
],
|
|
18198
|
+
onPress: handleReopen,
|
|
18199
|
+
disabled: isSubmitting || !reopenReason.trim(),
|
|
18200
|
+
activeOpacity: 0.7
|
|
18201
|
+
},
|
|
18202
|
+
isSubmitting ? /* @__PURE__ */ React17.createElement(ActivityIndicator2, { size: "small", color: "#fff" }) : /* @__PURE__ */ React17.createElement(Text15, { style: [
|
|
18203
|
+
styles5.reopenSubmitText,
|
|
18204
|
+
!reopenReason.trim() && styles5.reopenSubmitTextDisabled
|
|
18205
|
+
] }, "Reopen Issue")
|
|
18206
|
+
), /* @__PURE__ */ React17.createElement(
|
|
18207
|
+
TouchableOpacity14,
|
|
18208
|
+
{
|
|
18209
|
+
style: styles5.reopenCancelButton,
|
|
18210
|
+
onPress: () => {
|
|
18211
|
+
setShowReopenForm(false);
|
|
18212
|
+
setReopenReason("");
|
|
18213
|
+
setReopenError(null);
|
|
18214
|
+
},
|
|
18215
|
+
disabled: isSubmitting,
|
|
18216
|
+
activeOpacity: 0.7
|
|
18217
|
+
},
|
|
18218
|
+
/* @__PURE__ */ React17.createElement(Text15, { style: styles5.reopenCancelText }, "Cancel")
|
|
18219
|
+
))), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ React17.createElement(View17, { style: styles5.screenshotSection }, /* @__PURE__ */ React17.createElement(Text15, { style: styles5.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ React17.createElement(View17, { style: styles5.screenshotRow }, issue.screenshotUrls.map((url, i) => /* @__PURE__ */ React17.createElement(TouchableOpacity14, { key: i, onPress: () => Linking4.openURL(url), activeOpacity: 0.7 }, /* @__PURE__ */ React17.createElement(Image3, { source: { uri: url }, style: styles5.screenshotThumb }))))), /* @__PURE__ */ React17.createElement(View17, { style: styles5.metaSection }, issue.reporterName && /* @__PURE__ */ React17.createElement(Text15, { style: styles5.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ React17.createElement(Text15, { style: styles5.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))), dashboardUrl && /* @__PURE__ */ React17.createElement(
|
|
17696
18220
|
TouchableOpacity14,
|
|
17697
18221
|
{
|
|
17698
18222
|
style: styles5.dashboardLink,
|
|
@@ -17744,6 +18268,28 @@ function createStyles12() {
|
|
|
17744
18268
|
color: colors.textSecondary,
|
|
17745
18269
|
lineHeight: 19
|
|
17746
18270
|
},
|
|
18271
|
+
reopenedCard: {
|
|
18272
|
+
backgroundColor: colors.yellowDark,
|
|
18273
|
+
borderWidth: 1,
|
|
18274
|
+
borderColor: colors.yellowBorder,
|
|
18275
|
+
borderRadius: 8,
|
|
18276
|
+
padding: 12,
|
|
18277
|
+
marginBottom: 12
|
|
18278
|
+
},
|
|
18279
|
+
reopenedRow: {
|
|
18280
|
+
flexDirection: "row",
|
|
18281
|
+
alignItems: "center",
|
|
18282
|
+
gap: 8
|
|
18283
|
+
},
|
|
18284
|
+
reopenedIcon: {
|
|
18285
|
+
fontSize: 14
|
|
18286
|
+
},
|
|
18287
|
+
reopenedText: {
|
|
18288
|
+
fontSize: 13,
|
|
18289
|
+
fontWeight: "600",
|
|
18290
|
+
color: colors.yellowLight,
|
|
18291
|
+
flex: 1
|
|
18292
|
+
},
|
|
17747
18293
|
verifiedCard: {
|
|
17748
18294
|
backgroundColor: colors.greenDark,
|
|
17749
18295
|
borderWidth: 1,
|
|
@@ -17787,7 +18333,7 @@ function createStyles12() {
|
|
|
17787
18333
|
originalBugIcon: {
|
|
17788
18334
|
fontSize: 16
|
|
17789
18335
|
},
|
|
17790
|
-
|
|
18336
|
+
originalBugTitleText: {
|
|
17791
18337
|
fontSize: 13,
|
|
17792
18338
|
fontWeight: "600",
|
|
17793
18339
|
color: colors.yellowLight
|
|
@@ -17796,6 +18342,89 @@ function createStyles12() {
|
|
|
17796
18342
|
fontSize: 12,
|
|
17797
18343
|
color: colors.yellowSubtle
|
|
17798
18344
|
},
|
|
18345
|
+
reopenButton: {
|
|
18346
|
+
borderWidth: 1,
|
|
18347
|
+
borderColor: colors.orange,
|
|
18348
|
+
borderRadius: 8,
|
|
18349
|
+
paddingVertical: 10,
|
|
18350
|
+
paddingHorizontal: 16,
|
|
18351
|
+
alignItems: "center",
|
|
18352
|
+
marginBottom: 12
|
|
18353
|
+
},
|
|
18354
|
+
reopenButtonText: {
|
|
18355
|
+
fontSize: 13,
|
|
18356
|
+
fontWeight: "600",
|
|
18357
|
+
color: colors.orange
|
|
18358
|
+
},
|
|
18359
|
+
reopenForm: {
|
|
18360
|
+
backgroundColor: colors.card,
|
|
18361
|
+
borderWidth: 1,
|
|
18362
|
+
borderColor: colors.orange,
|
|
18363
|
+
borderRadius: 8,
|
|
18364
|
+
padding: 12,
|
|
18365
|
+
marginBottom: 12
|
|
18366
|
+
},
|
|
18367
|
+
reopenFormTitle: {
|
|
18368
|
+
fontSize: 13,
|
|
18369
|
+
fontWeight: "600",
|
|
18370
|
+
color: colors.orange,
|
|
18371
|
+
marginBottom: 8
|
|
18372
|
+
},
|
|
18373
|
+
reopenInput: {
|
|
18374
|
+
backgroundColor: colors.bg,
|
|
18375
|
+
borderWidth: 1,
|
|
18376
|
+
borderColor: colors.border,
|
|
18377
|
+
borderRadius: 6,
|
|
18378
|
+
padding: 8,
|
|
18379
|
+
color: colors.textPrimary,
|
|
18380
|
+
fontSize: 13,
|
|
18381
|
+
lineHeight: 18,
|
|
18382
|
+
minHeight: 60,
|
|
18383
|
+
textAlignVertical: "top"
|
|
18384
|
+
},
|
|
18385
|
+
reopenErrorText: {
|
|
18386
|
+
fontSize: 12,
|
|
18387
|
+
color: colors.red,
|
|
18388
|
+
marginTop: 6
|
|
18389
|
+
},
|
|
18390
|
+
reopenActions: {
|
|
18391
|
+
flexDirection: "row",
|
|
18392
|
+
gap: 8,
|
|
18393
|
+
marginTop: 8
|
|
18394
|
+
},
|
|
18395
|
+
reopenSubmitButton: {
|
|
18396
|
+
flex: 1,
|
|
18397
|
+
backgroundColor: colors.orange,
|
|
18398
|
+
borderRadius: 6,
|
|
18399
|
+
paddingVertical: 8,
|
|
18400
|
+
paddingHorizontal: 12,
|
|
18401
|
+
alignItems: "center",
|
|
18402
|
+
justifyContent: "center"
|
|
18403
|
+
},
|
|
18404
|
+
reopenSubmitDisabled: {
|
|
18405
|
+
backgroundColor: colors.card,
|
|
18406
|
+
opacity: 0.7
|
|
18407
|
+
},
|
|
18408
|
+
reopenSubmitText: {
|
|
18409
|
+
fontSize: 13,
|
|
18410
|
+
fontWeight: "600",
|
|
18411
|
+
color: "#fff"
|
|
18412
|
+
},
|
|
18413
|
+
reopenSubmitTextDisabled: {
|
|
18414
|
+
color: colors.textMuted
|
|
18415
|
+
},
|
|
18416
|
+
reopenCancelButton: {
|
|
18417
|
+
borderWidth: 1,
|
|
18418
|
+
borderColor: colors.border,
|
|
18419
|
+
borderRadius: 6,
|
|
18420
|
+
paddingVertical: 8,
|
|
18421
|
+
paddingHorizontal: 12,
|
|
18422
|
+
alignItems: "center"
|
|
18423
|
+
},
|
|
18424
|
+
reopenCancelText: {
|
|
18425
|
+
fontSize: 13,
|
|
18426
|
+
color: colors.textSecondary
|
|
18427
|
+
},
|
|
17799
18428
|
screenshotSection: {
|
|
17800
18429
|
marginBottom: 12
|
|
17801
18430
|
},
|
|
@@ -17847,18 +18476,20 @@ function createStyles12() {
|
|
|
17847
18476
|
}
|
|
17848
18477
|
|
|
17849
18478
|
// src/widget/screens/SessionStartScreen.tsx
|
|
17850
|
-
import React18, { useState as
|
|
17851
|
-
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";
|
|
17852
18481
|
function SessionStartScreen({ nav }) {
|
|
17853
18482
|
const { startSession, assignments, widgetColorScheme } = useBugBear();
|
|
17854
18483
|
const styles5 = useMemo13(() => createStyles13(), [widgetColorScheme]);
|
|
17855
|
-
const [focusArea, setFocusArea] =
|
|
17856
|
-
const [isStarting, setIsStarting] =
|
|
18484
|
+
const [focusArea, setFocusArea] = useState15("");
|
|
18485
|
+
const [isStarting, setIsStarting] = useState15(false);
|
|
18486
|
+
const [error, setError] = useState15(null);
|
|
17857
18487
|
const trackNames = Array.from(new Set(
|
|
17858
18488
|
assignments.filter((a) => a.testCase.track?.name).map((a) => a.testCase.track.name)
|
|
17859
18489
|
)).slice(0, 6);
|
|
17860
18490
|
const handleStart = async () => {
|
|
17861
18491
|
if (isStarting) return;
|
|
18492
|
+
setError(null);
|
|
17862
18493
|
Keyboard2.dismiss();
|
|
17863
18494
|
setIsStarting(true);
|
|
17864
18495
|
try {
|
|
@@ -17867,13 +18498,17 @@ function SessionStartScreen({ nav }) {
|
|
|
17867
18498
|
});
|
|
17868
18499
|
if (result.success) {
|
|
17869
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);
|
|
17870
18505
|
}
|
|
17871
18506
|
} finally {
|
|
17872
18507
|
setIsStarting(false);
|
|
17873
18508
|
}
|
|
17874
18509
|
};
|
|
17875
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(
|
|
17876
|
-
|
|
18511
|
+
TextInput10,
|
|
17877
18512
|
{
|
|
17878
18513
|
value: focusArea,
|
|
17879
18514
|
onChangeText: setFocusArea,
|
|
@@ -17892,7 +18527,7 @@ function SessionStartScreen({ nav }) {
|
|
|
17892
18527
|
activeOpacity: 0.7
|
|
17893
18528
|
},
|
|
17894
18529
|
/* @__PURE__ */ React18.createElement(Text16, { style: [styles5.chipText, focusArea === name && styles5.chipTextActive] }, name)
|
|
17895
|
-
)))), /* @__PURE__ */ React18.createElement(
|
|
18530
|
+
)))), error && /* @__PURE__ */ React18.createElement(View18, { style: styles5.errorBox }, /* @__PURE__ */ React18.createElement(Text16, { style: styles5.errorText }, error)), /* @__PURE__ */ React18.createElement(
|
|
17896
18531
|
TouchableOpacity15,
|
|
17897
18532
|
{
|
|
17898
18533
|
style: [styles5.startBtn, isStarting && styles5.startBtnDisabled],
|
|
@@ -17971,6 +18606,20 @@ function createStyles13() {
|
|
|
17971
18606
|
chipTextActive: {
|
|
17972
18607
|
color: colors.blueLight
|
|
17973
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
|
+
},
|
|
17974
18623
|
startBtn: {
|
|
17975
18624
|
paddingVertical: 14,
|
|
17976
18625
|
backgroundColor: colors.blueSurface,
|
|
@@ -17991,7 +18640,7 @@ function createStyles13() {
|
|
|
17991
18640
|
}
|
|
17992
18641
|
|
|
17993
18642
|
// src/widget/screens/SessionActiveScreen.tsx
|
|
17994
|
-
import React19, { useState as
|
|
18643
|
+
import React19, { useState as useState16, useEffect as useEffect11, useRef as useRef5, useMemo as useMemo14 } from "react";
|
|
17995
18644
|
import { View as View19, Text as Text17, TouchableOpacity as TouchableOpacity16, StyleSheet as StyleSheet19 } from "react-native";
|
|
17996
18645
|
function SessionActiveScreen({ nav }) {
|
|
17997
18646
|
const { activeSession, sessionFindings, endSession, refreshSession, widgetColorScheme } = useBugBear();
|
|
@@ -18002,8 +18651,8 @@ function SessionActiveScreen({ nav }) {
|
|
|
18002
18651
|
suggestion: { icon: "\u{1F4A1}", label: "Suggestion", color: colors.blue },
|
|
18003
18652
|
question: { icon: "\u2753", label: "Question", color: colors.violet }
|
|
18004
18653
|
}), [widgetColorScheme]);
|
|
18005
|
-
const [isEnding, setIsEnding] =
|
|
18006
|
-
const [elapsed, setElapsed] =
|
|
18654
|
+
const [isEnding, setIsEnding] = useState16(false);
|
|
18655
|
+
const [elapsed, setElapsed] = useState16(0);
|
|
18007
18656
|
const timerRef = useRef5(null);
|
|
18008
18657
|
useEffect11(() => {
|
|
18009
18658
|
refreshSession();
|
|
@@ -18228,8 +18877,8 @@ function createStyles14() {
|
|
|
18228
18877
|
}
|
|
18229
18878
|
|
|
18230
18879
|
// src/widget/screens/SessionFindingScreen.tsx
|
|
18231
|
-
import React20, { useState as
|
|
18232
|
-
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";
|
|
18233
18882
|
var FINDING_TYPES = [
|
|
18234
18883
|
{ value: "bug", icon: "\u{1F41B}", label: "Bug" },
|
|
18235
18884
|
{ value: "concern", icon: "\u26A0\uFE0F", label: "Concern" },
|
|
@@ -18246,11 +18895,11 @@ function SessionFindingScreen({ nav }) {
|
|
|
18246
18895
|
{ value: "low", label: "Low", color: colors.textMuted },
|
|
18247
18896
|
{ value: "observation", label: "Note", color: colors.textDim }
|
|
18248
18897
|
], [widgetColorScheme]);
|
|
18249
|
-
const [type, setType] =
|
|
18250
|
-
const [severity, setSeverity] =
|
|
18251
|
-
const [title, setTitle] =
|
|
18252
|
-
const [description, setDescription] =
|
|
18253
|
-
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);
|
|
18254
18903
|
const handleSubmit = async () => {
|
|
18255
18904
|
if (!title.trim() || isSubmitting) return;
|
|
18256
18905
|
Keyboard3.dismiss();
|
|
@@ -18293,7 +18942,7 @@ function SessionFindingScreen({ nav }) {
|
|
|
18293
18942
|
},
|
|
18294
18943
|
/* @__PURE__ */ React20.createElement(Text18, { style: [styles5.severityText, severity === s2.value && { color: s2.color }] }, s2.label)
|
|
18295
18944
|
)))), /* @__PURE__ */ React20.createElement(View20, { style: styles5.inputSection }, /* @__PURE__ */ React20.createElement(
|
|
18296
|
-
|
|
18945
|
+
TextInput11,
|
|
18297
18946
|
{
|
|
18298
18947
|
value: title,
|
|
18299
18948
|
onChangeText: setTitle,
|
|
@@ -18303,7 +18952,7 @@ function SessionFindingScreen({ nav }) {
|
|
|
18303
18952
|
returnKeyType: "next"
|
|
18304
18953
|
}
|
|
18305
18954
|
)), /* @__PURE__ */ React20.createElement(View20, { style: styles5.inputSection }, /* @__PURE__ */ React20.createElement(
|
|
18306
|
-
|
|
18955
|
+
TextInput11,
|
|
18307
18956
|
{
|
|
18308
18957
|
value: description,
|
|
18309
18958
|
onChangeText: setDescription,
|
|
@@ -18448,7 +19097,7 @@ function BugBearButton({
|
|
|
18448
19097
|
}) {
|
|
18449
19098
|
const { shouldShowWidget, testerInfo, isLoading, unreadCount, assignments, widgetMode, widgetColorScheme } = useBugBear();
|
|
18450
19099
|
const { currentScreen, canGoBack, push, pop, replace, reset } = useNavigation();
|
|
18451
|
-
const [modalVisible, setModalVisible] =
|
|
19100
|
+
const [modalVisible, setModalVisible] = useState18(false);
|
|
18452
19101
|
const styles5 = useMemo16(() => createStyles16(), [widgetColorScheme]);
|
|
18453
19102
|
const screenCaptureRef = useRef6(null);
|
|
18454
19103
|
const openModal = () => {
|
|
@@ -18656,14 +19305,14 @@ function BugBearButton({
|
|
|
18656
19305
|
style: styles5.modalOverlay
|
|
18657
19306
|
},
|
|
18658
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(
|
|
18659
|
-
|
|
19308
|
+
ScrollView4,
|
|
18660
19309
|
{
|
|
18661
19310
|
style: styles5.content,
|
|
18662
19311
|
contentContainerStyle: styles5.contentContainer,
|
|
18663
19312
|
keyboardShouldPersistTaps: "handled",
|
|
18664
19313
|
showsVerticalScrollIndicator: false
|
|
18665
19314
|
},
|
|
18666
|
-
isLoading ? /* @__PURE__ */ React21.createElement(View21, { style: styles5.loadingContainer }, /* @__PURE__ */ React21.createElement(
|
|
19315
|
+
isLoading ? /* @__PURE__ */ React21.createElement(View21, { style: styles5.loadingContainer }, /* @__PURE__ */ React21.createElement(ActivityIndicator3, { size: "large", color: colors.blue }), /* @__PURE__ */ React21.createElement(Text19, { style: styles5.loadingText }, "Loading...")) : renderScreen()
|
|
18667
19316
|
))
|
|
18668
19317
|
)
|
|
18669
19318
|
));
|