@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.js
CHANGED
|
@@ -12230,10 +12230,14 @@ var BugBearClient = class {
|
|
|
12230
12230
|
this.navigationHistory = [];
|
|
12231
12231
|
this.reportSubmitInFlight = false;
|
|
12232
12232
|
this._queue = null;
|
|
12233
|
+
this._sessionStorage = new LocalStorageAdapter();
|
|
12233
12234
|
this.realtimeChannels = [];
|
|
12234
12235
|
this.monitor = null;
|
|
12235
12236
|
this.initialized = false;
|
|
12236
12237
|
this.initError = null;
|
|
12238
|
+
this._presenceSessionId = null;
|
|
12239
|
+
this._presenceInterval = null;
|
|
12240
|
+
this._presencePaused = false;
|
|
12237
12241
|
this.config = config;
|
|
12238
12242
|
if (config.apiKey) {
|
|
12239
12243
|
this.pendingInit = this.resolveFromApiKey(config.apiKey);
|
|
@@ -12307,6 +12311,10 @@ var BugBearClient = class {
|
|
|
12307
12311
|
this.initOfflineQueue();
|
|
12308
12312
|
this.initMonitoring();
|
|
12309
12313
|
}
|
|
12314
|
+
/** Cache key scoped to the active project. */
|
|
12315
|
+
get sessionCacheKey() {
|
|
12316
|
+
return `bugbear_session_${this.config.projectId ?? "unknown"}`;
|
|
12317
|
+
}
|
|
12310
12318
|
/** Initialize offline queue if configured. Shared by both init paths. */
|
|
12311
12319
|
initOfflineQueue() {
|
|
12312
12320
|
if (this.config.offlineQueue?.enabled) {
|
|
@@ -12370,6 +12378,26 @@ var BugBearClient = class {
|
|
|
12370
12378
|
await this.pendingInit;
|
|
12371
12379
|
if (this.initError) throw this.initError;
|
|
12372
12380
|
}
|
|
12381
|
+
/**
|
|
12382
|
+
* Fire-and-forget call to a dashboard notification endpoint.
|
|
12383
|
+
* Only works when apiKey is configured (needed for API auth).
|
|
12384
|
+
* Failures are silently ignored — notifications are best-effort.
|
|
12385
|
+
*/
|
|
12386
|
+
async notifyDashboard(path, body) {
|
|
12387
|
+
if (!this.config.apiKey) return;
|
|
12388
|
+
try {
|
|
12389
|
+
const baseUrl = (this.config.apiBaseUrl || DEFAULT_API_BASE_URL).replace(/\/$/, "");
|
|
12390
|
+
await fetch(`${baseUrl}/api/v1/notifications/${path}`, {
|
|
12391
|
+
method: "POST",
|
|
12392
|
+
headers: {
|
|
12393
|
+
"Content-Type": "application/json",
|
|
12394
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
12395
|
+
},
|
|
12396
|
+
body: JSON.stringify(body)
|
|
12397
|
+
});
|
|
12398
|
+
} catch {
|
|
12399
|
+
}
|
|
12400
|
+
}
|
|
12373
12401
|
// ── Offline Queue ─────────────────────────────────────────
|
|
12374
12402
|
/**
|
|
12375
12403
|
* Access the offline queue (if enabled).
|
|
@@ -12396,6 +12424,48 @@ var BugBearClient = class {
|
|
|
12396
12424
|
}
|
|
12397
12425
|
await this._queue.load();
|
|
12398
12426
|
}
|
|
12427
|
+
// ── Session Cache ──────────────────────────────────────────
|
|
12428
|
+
/**
|
|
12429
|
+
* Swap the session cache storage adapter (for React Native — pass AsyncStorage).
|
|
12430
|
+
* Must be called before getCachedSession() for persistence across app kills.
|
|
12431
|
+
* Web callers don't need this — LocalStorageAdapter is the default.
|
|
12432
|
+
*/
|
|
12433
|
+
setSessionStorage(adapter) {
|
|
12434
|
+
this._sessionStorage = adapter;
|
|
12435
|
+
}
|
|
12436
|
+
/**
|
|
12437
|
+
* Cache the active QA session locally for instant restore on app restart.
|
|
12438
|
+
* Pass null to clear the cache (e.g. after ending a session).
|
|
12439
|
+
*/
|
|
12440
|
+
async cacheSession(session) {
|
|
12441
|
+
try {
|
|
12442
|
+
if (session) {
|
|
12443
|
+
await this._sessionStorage.setItem(this.sessionCacheKey, JSON.stringify(session));
|
|
12444
|
+
} else {
|
|
12445
|
+
await this._sessionStorage.removeItem(this.sessionCacheKey);
|
|
12446
|
+
}
|
|
12447
|
+
} catch {
|
|
12448
|
+
}
|
|
12449
|
+
}
|
|
12450
|
+
/**
|
|
12451
|
+
* Retrieve the cached QA session. Returns null if no cache, if stale (>24h),
|
|
12452
|
+
* or if parsing fails. The DB fetch in initializeBugBear() is the source of truth.
|
|
12453
|
+
*/
|
|
12454
|
+
async getCachedSession() {
|
|
12455
|
+
try {
|
|
12456
|
+
const raw = await this._sessionStorage.getItem(this.sessionCacheKey);
|
|
12457
|
+
if (!raw) return null;
|
|
12458
|
+
const session = JSON.parse(raw);
|
|
12459
|
+
const age = Date.now() - new Date(session.startedAt).getTime();
|
|
12460
|
+
if (age > 24 * 60 * 60 * 1e3) {
|
|
12461
|
+
await this._sessionStorage.removeItem(this.sessionCacheKey);
|
|
12462
|
+
return null;
|
|
12463
|
+
}
|
|
12464
|
+
return session;
|
|
12465
|
+
} catch {
|
|
12466
|
+
return null;
|
|
12467
|
+
}
|
|
12468
|
+
}
|
|
12399
12469
|
registerQueueHandlers() {
|
|
12400
12470
|
if (!this._queue) return;
|
|
12401
12471
|
this._queue.registerHandler("report", async (payload) => {
|
|
@@ -12413,6 +12483,11 @@ var BugBearClient = class {
|
|
|
12413
12483
|
if (error) return { success: false, error: error.message };
|
|
12414
12484
|
return { success: true };
|
|
12415
12485
|
});
|
|
12486
|
+
this._queue.registerHandler("email_capture", async (payload) => {
|
|
12487
|
+
const { error } = await this.supabase.from("email_captures").insert(payload).select("id").single();
|
|
12488
|
+
if (error) return { success: false, error: error.message };
|
|
12489
|
+
return { success: true };
|
|
12490
|
+
});
|
|
12416
12491
|
}
|
|
12417
12492
|
// ── Realtime Subscriptions ─────────────────────────────────
|
|
12418
12493
|
/** Whether realtime is enabled in config. */
|
|
@@ -12600,6 +12675,8 @@ var BugBearClient = class {
|
|
|
12600
12675
|
if (this.config.onReportSubmitted) {
|
|
12601
12676
|
this.config.onReportSubmitted(report);
|
|
12602
12677
|
}
|
|
12678
|
+
this.notifyDashboard("report", { reportId: data.id }).catch(() => {
|
|
12679
|
+
});
|
|
12603
12680
|
return { success: true, reportId: data.id };
|
|
12604
12681
|
} catch (err) {
|
|
12605
12682
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -12612,6 +12689,44 @@ var BugBearClient = class {
|
|
|
12612
12689
|
this.reportSubmitInFlight = false;
|
|
12613
12690
|
}
|
|
12614
12691
|
}
|
|
12692
|
+
/**
|
|
12693
|
+
* Capture an email for QA testing.
|
|
12694
|
+
* Called by the email interceptor — not typically called directly.
|
|
12695
|
+
*/
|
|
12696
|
+
async captureEmail(payload) {
|
|
12697
|
+
try {
|
|
12698
|
+
await this.ready();
|
|
12699
|
+
if (!payload.subject || !payload.to || payload.to.length === 0) {
|
|
12700
|
+
return { success: false, error: "subject and to are required" };
|
|
12701
|
+
}
|
|
12702
|
+
const record = {
|
|
12703
|
+
project_id: this.config.projectId,
|
|
12704
|
+
to_addresses: payload.to,
|
|
12705
|
+
from_address: payload.from || null,
|
|
12706
|
+
subject: payload.subject,
|
|
12707
|
+
html_content: payload.html || null,
|
|
12708
|
+
text_content: payload.text || null,
|
|
12709
|
+
template_id: payload.templateId || null,
|
|
12710
|
+
metadata: payload.metadata || {},
|
|
12711
|
+
capture_mode: payload.captureMode,
|
|
12712
|
+
was_delivered: payload.wasDelivered,
|
|
12713
|
+
delivery_status: payload.wasDelivered ? "sent" : "pending"
|
|
12714
|
+
};
|
|
12715
|
+
const { data, error } = await this.supabase.from("email_captures").insert(record).select("id").single();
|
|
12716
|
+
if (error) {
|
|
12717
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
12718
|
+
await this._queue.enqueue("email_capture", record);
|
|
12719
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
12720
|
+
}
|
|
12721
|
+
console.error("BugBear: Failed to capture email", error.message);
|
|
12722
|
+
return { success: false, error: error.message };
|
|
12723
|
+
}
|
|
12724
|
+
return { success: true, captureId: data.id };
|
|
12725
|
+
} catch (err) {
|
|
12726
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
12727
|
+
return { success: false, error: message };
|
|
12728
|
+
}
|
|
12729
|
+
}
|
|
12615
12730
|
/**
|
|
12616
12731
|
* Get assigned tests for current user
|
|
12617
12732
|
* First looks up the tester by email, then fetches their assignments
|
|
@@ -13233,6 +13348,32 @@ var BugBearClient = class {
|
|
|
13233
13348
|
return [];
|
|
13234
13349
|
}
|
|
13235
13350
|
}
|
|
13351
|
+
/**
|
|
13352
|
+
* Reopen a done issue that the tester believes isn't actually fixed.
|
|
13353
|
+
* Transitions the report from a done status back to 'confirmed'.
|
|
13354
|
+
*/
|
|
13355
|
+
async reopenReport(reportId, reason) {
|
|
13356
|
+
try {
|
|
13357
|
+
const testerInfo = await this.getTesterInfo();
|
|
13358
|
+
if (!testerInfo) return { success: false, error: "Not authenticated as tester" };
|
|
13359
|
+
const { data, error } = await this.supabase.rpc("reopen_report", {
|
|
13360
|
+
p_report_id: reportId,
|
|
13361
|
+
p_tester_id: testerInfo.id,
|
|
13362
|
+
p_reason: reason
|
|
13363
|
+
});
|
|
13364
|
+
if (error) {
|
|
13365
|
+
console.error("BugBear: Failed to reopen report", formatPgError(error));
|
|
13366
|
+
return { success: false, error: error.message };
|
|
13367
|
+
}
|
|
13368
|
+
if (!data?.success) {
|
|
13369
|
+
return { success: false, error: data?.error || "Failed to reopen report" };
|
|
13370
|
+
}
|
|
13371
|
+
return { success: true };
|
|
13372
|
+
} catch (err) {
|
|
13373
|
+
console.error("BugBear: Error reopening report", err);
|
|
13374
|
+
return { success: false, error: "Unexpected error" };
|
|
13375
|
+
}
|
|
13376
|
+
}
|
|
13236
13377
|
/**
|
|
13237
13378
|
* Basic email format validation (defense in depth)
|
|
13238
13379
|
*/
|
|
@@ -13742,6 +13883,7 @@ var BugBearClient = class {
|
|
|
13742
13883
|
lastMessageAt: row.last_message_at,
|
|
13743
13884
|
createdAt: row.created_at,
|
|
13744
13885
|
unreadCount: Number(row.unread_count) || 0,
|
|
13886
|
+
reporterName: row.reporter_name || void 0,
|
|
13745
13887
|
lastMessage: row.last_message_preview ? {
|
|
13746
13888
|
id: "",
|
|
13747
13889
|
threadId: row.thread_id,
|
|
@@ -13819,7 +13961,7 @@ var BugBearClient = class {
|
|
|
13819
13961
|
insertData.attachments = safeAttachments;
|
|
13820
13962
|
}
|
|
13821
13963
|
}
|
|
13822
|
-
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
13964
|
+
const { data: msgData, error } = await this.supabase.from("discussion_messages").insert(insertData).select("id").single();
|
|
13823
13965
|
if (error) {
|
|
13824
13966
|
if (this._queue && isNetworkError(error.message)) {
|
|
13825
13967
|
await this._queue.enqueue("message", insertData);
|
|
@@ -13828,6 +13970,10 @@ var BugBearClient = class {
|
|
|
13828
13970
|
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
13829
13971
|
return false;
|
|
13830
13972
|
}
|
|
13973
|
+
if (msgData?.id) {
|
|
13974
|
+
this.notifyDashboard("message", { threadId, messageId: msgData.id }).catch(() => {
|
|
13975
|
+
});
|
|
13976
|
+
}
|
|
13831
13977
|
await this.markThreadAsRead(threadId);
|
|
13832
13978
|
return true;
|
|
13833
13979
|
} catch (err) {
|
|
@@ -13953,6 +14099,7 @@ var BugBearClient = class {
|
|
|
13953
14099
|
if (!session) {
|
|
13954
14100
|
return { success: false, error: "Session created but could not be fetched" };
|
|
13955
14101
|
}
|
|
14102
|
+
await this.cacheSession(session);
|
|
13956
14103
|
return { success: true, session };
|
|
13957
14104
|
} catch (err) {
|
|
13958
14105
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -13976,6 +14123,7 @@ var BugBearClient = class {
|
|
|
13976
14123
|
return { success: false, error: error.message };
|
|
13977
14124
|
}
|
|
13978
14125
|
const session = this.transformSession(data);
|
|
14126
|
+
await this.cacheSession(null);
|
|
13979
14127
|
return { success: true, session };
|
|
13980
14128
|
} catch (err) {
|
|
13981
14129
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -13991,8 +14139,13 @@ var BugBearClient = class {
|
|
|
13991
14139
|
const testerInfo = await this.getTesterInfo();
|
|
13992
14140
|
if (!testerInfo) return null;
|
|
13993
14141
|
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();
|
|
13994
|
-
if (error || !data)
|
|
13995
|
-
|
|
14142
|
+
if (error || !data) {
|
|
14143
|
+
await this.cacheSession(null);
|
|
14144
|
+
return null;
|
|
14145
|
+
}
|
|
14146
|
+
const session = this.transformSession(data);
|
|
14147
|
+
await this.cacheSession(session);
|
|
14148
|
+
return session;
|
|
13996
14149
|
} catch (err) {
|
|
13997
14150
|
console.error("BugBear: Error fetching active session", err);
|
|
13998
14151
|
return null;
|
|
@@ -14170,6 +14323,93 @@ var BugBearClient = class {
|
|
|
14170
14323
|
updatedAt: data.updated_at
|
|
14171
14324
|
};
|
|
14172
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
|
+
}
|
|
14173
14413
|
};
|
|
14174
14414
|
function createBugBear(config) {
|
|
14175
14415
|
return new BugBearClient(config);
|
|
@@ -14304,6 +14544,9 @@ function setActiveColors(palette) {
|
|
|
14304
14544
|
colors = palette;
|
|
14305
14545
|
shared = createSharedStyles();
|
|
14306
14546
|
}
|
|
14547
|
+
function withAlpha(hex, alpha) {
|
|
14548
|
+
return hex + Math.round(alpha * 255).toString(16).padStart(2, "0");
|
|
14549
|
+
}
|
|
14307
14550
|
function createSharedStyles() {
|
|
14308
14551
|
return import_react_native.StyleSheet.create({
|
|
14309
14552
|
card: {
|
|
@@ -14510,6 +14753,7 @@ var BugBearContext = (0, import_react.createContext)({
|
|
|
14510
14753
|
issueCounts: { open: 0, done: 0, reopened: 0 },
|
|
14511
14754
|
refreshIssueCounts: async () => {
|
|
14512
14755
|
},
|
|
14756
|
+
reopenReport: async () => ({ success: false }),
|
|
14513
14757
|
queuedCount: 0,
|
|
14514
14758
|
dashboardUrl: void 0,
|
|
14515
14759
|
onError: void 0
|
|
@@ -14651,6 +14895,14 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14651
14895
|
const counts = await client.getIssueCounts();
|
|
14652
14896
|
setIssueCounts(counts);
|
|
14653
14897
|
}, [client]);
|
|
14898
|
+
const reopenReport = (0, import_react.useCallback)(async (reportId, reason) => {
|
|
14899
|
+
if (!client) return { success: false, error: "Client not initialized" };
|
|
14900
|
+
const result = await client.reopenReport(reportId, reason);
|
|
14901
|
+
if (result.success) {
|
|
14902
|
+
await refreshIssueCounts();
|
|
14903
|
+
}
|
|
14904
|
+
return result;
|
|
14905
|
+
}, [client, refreshIssueCounts]);
|
|
14654
14906
|
const initializeBugBear = (0, import_react.useCallback)(async (bugBearClient) => {
|
|
14655
14907
|
setIsLoading(true);
|
|
14656
14908
|
try {
|
|
@@ -14736,6 +14988,10 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14736
14988
|
setClient(newClient);
|
|
14737
14989
|
(async () => {
|
|
14738
14990
|
try {
|
|
14991
|
+
const cachedSession = await newClient.getCachedSession();
|
|
14992
|
+
if (cachedSession) {
|
|
14993
|
+
setActiveSession(cachedSession);
|
|
14994
|
+
}
|
|
14739
14995
|
await initializeBugBear(newClient);
|
|
14740
14996
|
if (newClient.monitor && config.monitoring) {
|
|
14741
14997
|
const getCurrentRoute = () => contextCapture.getCurrentRoute();
|
|
@@ -14793,6 +15049,28 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14793
15049
|
}
|
|
14794
15050
|
return () => subscription.remove();
|
|
14795
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]);
|
|
14796
15074
|
(0, import_react.useEffect)(() => {
|
|
14797
15075
|
if (!client || !isTester) return;
|
|
14798
15076
|
if (widgetMode === "qa" && !isQAEnabled) return;
|
|
@@ -14870,6 +15148,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14870
15148
|
// Issue tracking
|
|
14871
15149
|
issueCounts,
|
|
14872
15150
|
refreshIssueCounts,
|
|
15151
|
+
reopenReport,
|
|
14873
15152
|
queuedCount,
|
|
14874
15153
|
dashboardUrl: config.dashboardUrl,
|
|
14875
15154
|
onError: config.onError
|
|
@@ -15601,6 +15880,17 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
15601
15880
|
setShowSteps(true);
|
|
15602
15881
|
setShowDetails(false);
|
|
15603
15882
|
}, [displayedAssignment?.id]);
|
|
15883
|
+
(0, import_react5.useEffect)(() => {
|
|
15884
|
+
if (!client || !displayedAssignment || displayedAssignment.status !== "pending") return;
|
|
15885
|
+
let cancelled = false;
|
|
15886
|
+
(async () => {
|
|
15887
|
+
await client.updateAssignmentStatus(displayedAssignment.id, "in_progress");
|
|
15888
|
+
if (!cancelled) await refreshAssignments();
|
|
15889
|
+
})();
|
|
15890
|
+
return () => {
|
|
15891
|
+
cancelled = true;
|
|
15892
|
+
};
|
|
15893
|
+
}, [client, displayedAssignment?.id, displayedAssignment?.status, refreshAssignments]);
|
|
15604
15894
|
(0, import_react5.useEffect)(() => {
|
|
15605
15895
|
const active = displayedAssignment?.status === "in_progress" ? displayedAssignment : null;
|
|
15606
15896
|
if (!active?.startedAt) {
|
|
@@ -15651,17 +15941,6 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
15651
15941
|
setIsSubmitting(false);
|
|
15652
15942
|
}
|
|
15653
15943
|
}, [client, displayedAssignment, refreshAssignments, nav, isSubmitting]);
|
|
15654
|
-
const handleStart = (0, import_react5.useCallback)(async () => {
|
|
15655
|
-
if (!client || !displayedAssignment || isSubmitting) return;
|
|
15656
|
-
import_react_native5.Keyboard.dismiss();
|
|
15657
|
-
setIsSubmitting(true);
|
|
15658
|
-
try {
|
|
15659
|
-
await client.updateAssignmentStatus(displayedAssignment.id, "in_progress");
|
|
15660
|
-
await refreshAssignments();
|
|
15661
|
-
} finally {
|
|
15662
|
-
setIsSubmitting(false);
|
|
15663
|
-
}
|
|
15664
|
-
}, [client, displayedAssignment, refreshAssignments, isSubmitting]);
|
|
15665
15944
|
const handleReopen = (0, import_react5.useCallback)(async () => {
|
|
15666
15945
|
if (!client || !displayedAssignment || isSubmitting) return;
|
|
15667
15946
|
import_react_native5.Keyboard.dismiss();
|
|
@@ -15807,15 +16086,7 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
15807
16086
|
disabled: isSubmitting
|
|
15808
16087
|
},
|
|
15809
16088
|
/* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.passBtnText }, "Change to Pass")
|
|
15810
|
-
))) : displayedAssignment.
|
|
15811
|
-
import_react_native5.TouchableOpacity,
|
|
15812
|
-
{
|
|
15813
|
-
style: [styles5.startBtn, isSubmitting && { opacity: 0.5 }],
|
|
15814
|
-
onPress: handleStart,
|
|
15815
|
-
disabled: isSubmitting
|
|
15816
|
-
},
|
|
15817
|
-
/* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.startBtnText }, isSubmitting ? "Starting..." : displayedAssignment.isVerification ? "\u{1F50D} Start Verification" : "\u25B6 Start Test")
|
|
15818
|
-
) : /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles5.actionButtons }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles5.actionBtn, styles5.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.failBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Reporting..." : "Failing..." : displayedAssignment.isVerification ? "\u2717 Still Broken" : "Fail")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles5.actionBtn, styles5.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.skipBtnText }, "Skip")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles5.actionBtn, styles5.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.passBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Verifying..." : "Passing..." : displayedAssignment.isVerification ? "\u2713 Fix Verified" : "Pass"))), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles5.modalOverlay }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles5.modalContent }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.modalTitle }, "Skip this test?"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.modalSubtitle }, "Select a reason:"), [
|
|
16089
|
+
))) : /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles5.actionButtons }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles5.actionBtn, styles5.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.failBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Reporting..." : "Failing..." : displayedAssignment.isVerification ? "\u2717 Still Broken" : "Fail")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles5.actionBtn, styles5.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.skipBtnText }, "Skip")), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.TouchableOpacity, { style: [styles5.actionBtn, styles5.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.passBtnText }, isSubmitting ? displayedAssignment.isVerification ? "Verifying..." : "Passing..." : displayedAssignment.isVerification ? "\u2713 Fix Verified" : "Pass"))), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles5.modalOverlay }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.View, { style: styles5.modalContent }, /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.modalTitle }, "Skip this test?"), /* @__PURE__ */ import_react5.default.createElement(import_react_native5.Text, { style: styles5.modalSubtitle }, "Select a reason:"), [
|
|
15819
16090
|
{ reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
|
|
15820
16091
|
{ reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
|
|
15821
16092
|
{ reason: "dependency", label: "\u{1F517} Needs another test first" },
|
|
@@ -15929,9 +16200,6 @@ function createStyles2() {
|
|
|
15929
16200
|
completedLabel: { fontSize: 13, fontWeight: "600", color: colors.textSecondary },
|
|
15930
16201
|
reopenBtn: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.blue },
|
|
15931
16202
|
reopenBtnText: { fontSize: 14, fontWeight: "600", color: colors.blue },
|
|
15932
|
-
// Start Test button
|
|
15933
|
-
startBtn: { paddingVertical: 16, borderRadius: 12, alignItems: "center", backgroundColor: colors.blueSurface, borderWidth: 1, borderColor: colors.blueAccent, marginTop: 8 },
|
|
15934
|
-
startBtnText: { fontSize: 16, fontWeight: "600", color: colors.blueText },
|
|
15935
16203
|
// Action buttons
|
|
15936
16204
|
actionButtons: { flexDirection: "row", gap: 10, marginTop: 8 },
|
|
15937
16205
|
actionBtn: { flex: 1, paddingVertical: 14, borderRadius: 12, alignItems: "center" },
|
|
@@ -16800,6 +17068,8 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16800
17068
|
const [severity, setSeverity] = (0, import_react12.useState)("medium");
|
|
16801
17069
|
const [category, setCategory] = (0, import_react12.useState)(null);
|
|
16802
17070
|
const [description, setDescription] = (0, import_react12.useState)("");
|
|
17071
|
+
const [myIssues, setMyIssues] = (0, import_react12.useState)([]);
|
|
17072
|
+
const [similarReports, setSimilarReports] = (0, import_react12.useState)([]);
|
|
16803
17073
|
const [affectedScreen, setAffectedScreen] = (0, import_react12.useState)("");
|
|
16804
17074
|
const [submitting, setSubmitting] = (0, import_react12.useState)(false);
|
|
16805
17075
|
const [error, setError] = (0, import_react12.useState)(null);
|
|
@@ -16815,6 +17085,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16815
17085
|
}, [autoCaptureUri, onAutoCaptureConsumed]);
|
|
16816
17086
|
const isRetestFailure = prefill?.type === "test_fail";
|
|
16817
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]);
|
|
16818
17123
|
(0, import_react12.useEffect)(() => {
|
|
16819
17124
|
if (reportType === "feedback" || reportType === "suggestion") {
|
|
16820
17125
|
setCategory("other");
|
|
@@ -16889,7 +17194,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16889
17194
|
numberOfLines: 4,
|
|
16890
17195
|
textAlignVertical: "top"
|
|
16891
17196
|
}
|
|
16892
|
-
)
|
|
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(
|
|
16893
17232
|
ImagePickerButtons,
|
|
16894
17233
|
{
|
|
16895
17234
|
images: images.images,
|
|
@@ -16945,7 +17284,41 @@ function ReportScreen({ nav, prefill, autoCaptureUri, onAutoCaptureConsumed }) {
|
|
|
16945
17284
|
numberOfLines: 4,
|
|
16946
17285
|
textAlignVertical: "top"
|
|
16947
17286
|
}
|
|
16948
|
-
)
|
|
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(
|
|
16949
17322
|
import_react_native11.TextInput,
|
|
16950
17323
|
{
|
|
16951
17324
|
style: styles5.screenInput,
|
|
@@ -17026,6 +17399,19 @@ var import_react_native13 = require("react-native");
|
|
|
17026
17399
|
function MessageListScreen({ nav }) {
|
|
17027
17400
|
const { threads, unreadCount, refreshThreads, dashboardUrl, isLoading, widgetColorScheme } = useBugBear();
|
|
17028
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
|
+
];
|
|
17029
17415
|
if (isLoading) return /* @__PURE__ */ import_react14.default.createElement(MessageListScreenSkeleton, null);
|
|
17030
17416
|
return /* @__PURE__ */ import_react14.default.createElement(import_react_native13.View, null, /* @__PURE__ */ import_react14.default.createElement(
|
|
17031
17417
|
import_react_native13.TouchableOpacity,
|
|
@@ -17034,14 +17420,54 @@ function MessageListScreen({ nav }) {
|
|
|
17034
17420
|
onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
|
|
17035
17421
|
},
|
|
17036
17422
|
/* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.newMsgText }, "\u2709\uFE0F New Message")
|
|
17037
|
-
),
|
|
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(
|
|
17038
17464
|
import_react_native13.TouchableOpacity,
|
|
17039
17465
|
{
|
|
17040
17466
|
key: thread.id,
|
|
17041
17467
|
style: [styles5.threadItem, thread.unreadCount > 0 && styles5.threadItemUnread],
|
|
17042
17468
|
onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
|
|
17043
17469
|
},
|
|
17044
|
-
/* @__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))),
|
|
17045
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) }] }))
|
|
17046
17472
|
))), dashboardUrl && /* @__PURE__ */ import_react14.default.createElement(
|
|
17047
17473
|
import_react_native13.TouchableOpacity,
|
|
@@ -17051,7 +17477,7 @@ function MessageListScreen({ nav }) {
|
|
|
17051
17477
|
activeOpacity: 0.7
|
|
17052
17478
|
},
|
|
17053
17479
|
/* @__PURE__ */ import_react14.default.createElement(import_react_native13.Text, { style: styles5.dashboardLinkText }, "\u{1F310}", " View on Dashboard ", "\u2192")
|
|
17054
|
-
), /* @__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"))));
|
|
17055
17481
|
}
|
|
17056
17482
|
function createStyles7() {
|
|
17057
17483
|
return import_react_native13.StyleSheet.create({
|
|
@@ -17432,7 +17858,17 @@ function IssueListScreen({ nav, category }) {
|
|
|
17432
17858
|
const [loading, setLoading] = (0, import_react18.useState)(true);
|
|
17433
17859
|
const [counts, setCounts] = (0, import_react18.useState)(null);
|
|
17434
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)("");
|
|
17435
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]);
|
|
17436
17872
|
(0, import_react18.useEffect)(() => {
|
|
17437
17873
|
if (!client) return;
|
|
17438
17874
|
client.getIssueCounts().then(setCounts).catch(() => {
|
|
@@ -17472,6 +17908,9 @@ function IssueListScreen({ nav, category }) {
|
|
|
17472
17908
|
}
|
|
17473
17909
|
return sorted;
|
|
17474
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;
|
|
17475
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) => {
|
|
17476
17915
|
const catConfig = CATEGORY_CONFIG[cat];
|
|
17477
17916
|
const isActive = activeCategory === cat;
|
|
@@ -17518,7 +17957,25 @@ function IssueListScreen({ nav, category }) {
|
|
|
17518
17957
|
styles5.sortBtnText,
|
|
17519
17958
|
sortMode === s2.key && styles5.sortBtnTextActive
|
|
17520
17959
|
] }, s2.label)
|
|
17521
|
-
))),
|
|
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(
|
|
17522
17979
|
import_react_native17.TouchableOpacity,
|
|
17523
17980
|
{
|
|
17524
17981
|
key: issue.id,
|
|
@@ -17685,9 +18142,15 @@ function createStyles11() {
|
|
|
17685
18142
|
// src/widget/screens/IssueDetailScreen.tsx
|
|
17686
18143
|
var import_react19 = __toESM(require("react"));
|
|
17687
18144
|
var import_react_native18 = require("react-native");
|
|
18145
|
+
var DONE_STATUSES = ["verified", "resolved", "closed", "reviewed"];
|
|
17688
18146
|
function IssueDetailScreen({ nav, issue }) {
|
|
17689
|
-
const { dashboardUrl, widgetColorScheme } = useBugBear();
|
|
18147
|
+
const { dashboardUrl, widgetColorScheme, reopenReport } = useBugBear();
|
|
17690
18148
|
const styles5 = (0, import_react19.useMemo)(() => createStyles12(), [widgetColorScheme]);
|
|
18149
|
+
const [showReopenForm, setShowReopenForm] = (0, import_react19.useState)(false);
|
|
18150
|
+
const [reopenReason, setReopenReason] = (0, import_react19.useState)("");
|
|
18151
|
+
const [isSubmitting, setIsSubmitting] = (0, import_react19.useState)(false);
|
|
18152
|
+
const [reopenError, setReopenError] = (0, import_react19.useState)(null);
|
|
18153
|
+
const [wasReopened, setWasReopened] = (0, import_react19.useState)(false);
|
|
17691
18154
|
const STATUS_LABELS = (0, import_react19.useMemo)(() => ({
|
|
17692
18155
|
new: { label: "New", bg: colors.blueDark, color: colors.blueLight },
|
|
17693
18156
|
triaging: { label: "Triaging", bg: colors.blueDark, color: colors.blueLight },
|
|
@@ -17710,7 +18173,68 @@ function IssueDetailScreen({ nav, issue }) {
|
|
|
17710
18173
|
}), [widgetColorScheme]);
|
|
17711
18174
|
const statusConfig = STATUS_LABELS[issue.status] || { label: issue.status, bg: colors.card, color: colors.textSecondary };
|
|
17712
18175
|
const severityConfig = issue.severity ? SEVERITY_CONFIG[issue.severity] : null;
|
|
17713
|
-
|
|
18176
|
+
const isDone = DONE_STATUSES.includes(issue.status);
|
|
18177
|
+
const handleReopen = (0, import_react19.useCallback)(async () => {
|
|
18178
|
+
if (!reopenReason.trim()) return;
|
|
18179
|
+
setIsSubmitting(true);
|
|
18180
|
+
setReopenError(null);
|
|
18181
|
+
const result = await reopenReport(issue.id, reopenReason.trim());
|
|
18182
|
+
setIsSubmitting(false);
|
|
18183
|
+
if (result.success) {
|
|
18184
|
+
setWasReopened(true);
|
|
18185
|
+
setShowReopenForm(false);
|
|
18186
|
+
} else {
|
|
18187
|
+
setReopenError(result.error || "Failed to reopen");
|
|
18188
|
+
}
|
|
18189
|
+
}, [reopenReason, reopenReport, issue.id]);
|
|
18190
|
+
return /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, null, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.badgeRow }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: [styles5.badge, { backgroundColor: wasReopened ? colors.yellowDark : statusConfig.bg }] }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: [styles5.badgeText, { color: wasReopened ? colors.yellowLight : statusConfig.color }] }, wasReopened ? "Reopened" : statusConfig.label)), severityConfig && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: [styles5.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: [styles5.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.title }, issue.title), issue.route && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.route }, issue.route), issue.description && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.descriptionCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.descriptionText }, issue.description)), wasReopened && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.reopenedCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.reopenedRow }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.reopenedIcon }, "\u{1F504}"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.reopenedText }, "Issue reopened \u2014 your team has been notified"))), issue.verifiedByName && !wasReopened && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.verifiedCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.verifiedHeader }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.verifiedIcon }, "\u2705"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { 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__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.originalBugCard }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.originalBugHeader }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.originalBugTitleText }, "Original Bug")), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.originalBugBody }, "Retest of: ", issue.originalBugTitle)), isDone && !wasReopened && !showReopenForm && /* @__PURE__ */ import_react19.default.createElement(
|
|
18191
|
+
import_react_native18.TouchableOpacity,
|
|
18192
|
+
{
|
|
18193
|
+
style: styles5.reopenButton,
|
|
18194
|
+
onPress: () => setShowReopenForm(true),
|
|
18195
|
+
activeOpacity: 0.7
|
|
18196
|
+
},
|
|
18197
|
+
/* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.reopenButtonText }, "\u{1F504}", " Not Fixed \u2014 Reopen Issue")
|
|
18198
|
+
), showReopenForm && !wasReopened && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.reopenForm }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.reopenFormTitle }, "Why isn't this fixed?"), /* @__PURE__ */ import_react19.default.createElement(
|
|
18199
|
+
import_react_native18.TextInput,
|
|
18200
|
+
{
|
|
18201
|
+
value: reopenReason,
|
|
18202
|
+
onChangeText: setReopenReason,
|
|
18203
|
+
placeholder: "Describe what you're still seeing...",
|
|
18204
|
+
placeholderTextColor: colors.textMuted,
|
|
18205
|
+
style: styles5.reopenInput,
|
|
18206
|
+
multiline: true,
|
|
18207
|
+
autoFocus: true
|
|
18208
|
+
}
|
|
18209
|
+
), reopenError && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.reopenErrorText }, reopenError), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.reopenActions }, /* @__PURE__ */ import_react19.default.createElement(
|
|
18210
|
+
import_react_native18.TouchableOpacity,
|
|
18211
|
+
{
|
|
18212
|
+
style: [
|
|
18213
|
+
styles5.reopenSubmitButton,
|
|
18214
|
+
(!reopenReason.trim() || isSubmitting) && styles5.reopenSubmitDisabled
|
|
18215
|
+
],
|
|
18216
|
+
onPress: handleReopen,
|
|
18217
|
+
disabled: isSubmitting || !reopenReason.trim(),
|
|
18218
|
+
activeOpacity: 0.7
|
|
18219
|
+
},
|
|
18220
|
+
isSubmitting ? /* @__PURE__ */ import_react19.default.createElement(import_react_native18.ActivityIndicator, { size: "small", color: "#fff" }) : /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: [
|
|
18221
|
+
styles5.reopenSubmitText,
|
|
18222
|
+
!reopenReason.trim() && styles5.reopenSubmitTextDisabled
|
|
18223
|
+
] }, "Reopen Issue")
|
|
18224
|
+
), /* @__PURE__ */ import_react19.default.createElement(
|
|
18225
|
+
import_react_native18.TouchableOpacity,
|
|
18226
|
+
{
|
|
18227
|
+
style: styles5.reopenCancelButton,
|
|
18228
|
+
onPress: () => {
|
|
18229
|
+
setShowReopenForm(false);
|
|
18230
|
+
setReopenReason("");
|
|
18231
|
+
setReopenError(null);
|
|
18232
|
+
},
|
|
18233
|
+
disabled: isSubmitting,
|
|
18234
|
+
activeOpacity: 0.7
|
|
18235
|
+
},
|
|
18236
|
+
/* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.reopenCancelText }, "Cancel")
|
|
18237
|
+
))), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.screenshotSection }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.screenshotRow }, issue.screenshotUrls.map((url, i) => /* @__PURE__ */ import_react19.default.createElement(import_react_native18.TouchableOpacity, { key: i, onPress: () => import_react_native18.Linking.openURL(url), activeOpacity: 0.7 }, /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Image, { source: { uri: url }, style: styles5.screenshotThumb }))))), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.View, { style: styles5.metaSection }, issue.reporterName && /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))), dashboardUrl && /* @__PURE__ */ import_react19.default.createElement(
|
|
17714
18238
|
import_react_native18.TouchableOpacity,
|
|
17715
18239
|
{
|
|
17716
18240
|
style: styles5.dashboardLink,
|
|
@@ -17762,6 +18286,28 @@ function createStyles12() {
|
|
|
17762
18286
|
color: colors.textSecondary,
|
|
17763
18287
|
lineHeight: 19
|
|
17764
18288
|
},
|
|
18289
|
+
reopenedCard: {
|
|
18290
|
+
backgroundColor: colors.yellowDark,
|
|
18291
|
+
borderWidth: 1,
|
|
18292
|
+
borderColor: colors.yellowBorder,
|
|
18293
|
+
borderRadius: 8,
|
|
18294
|
+
padding: 12,
|
|
18295
|
+
marginBottom: 12
|
|
18296
|
+
},
|
|
18297
|
+
reopenedRow: {
|
|
18298
|
+
flexDirection: "row",
|
|
18299
|
+
alignItems: "center",
|
|
18300
|
+
gap: 8
|
|
18301
|
+
},
|
|
18302
|
+
reopenedIcon: {
|
|
18303
|
+
fontSize: 14
|
|
18304
|
+
},
|
|
18305
|
+
reopenedText: {
|
|
18306
|
+
fontSize: 13,
|
|
18307
|
+
fontWeight: "600",
|
|
18308
|
+
color: colors.yellowLight,
|
|
18309
|
+
flex: 1
|
|
18310
|
+
},
|
|
17765
18311
|
verifiedCard: {
|
|
17766
18312
|
backgroundColor: colors.greenDark,
|
|
17767
18313
|
borderWidth: 1,
|
|
@@ -17805,7 +18351,7 @@ function createStyles12() {
|
|
|
17805
18351
|
originalBugIcon: {
|
|
17806
18352
|
fontSize: 16
|
|
17807
18353
|
},
|
|
17808
|
-
|
|
18354
|
+
originalBugTitleText: {
|
|
17809
18355
|
fontSize: 13,
|
|
17810
18356
|
fontWeight: "600",
|
|
17811
18357
|
color: colors.yellowLight
|
|
@@ -17814,6 +18360,89 @@ function createStyles12() {
|
|
|
17814
18360
|
fontSize: 12,
|
|
17815
18361
|
color: colors.yellowSubtle
|
|
17816
18362
|
},
|
|
18363
|
+
reopenButton: {
|
|
18364
|
+
borderWidth: 1,
|
|
18365
|
+
borderColor: colors.orange,
|
|
18366
|
+
borderRadius: 8,
|
|
18367
|
+
paddingVertical: 10,
|
|
18368
|
+
paddingHorizontal: 16,
|
|
18369
|
+
alignItems: "center",
|
|
18370
|
+
marginBottom: 12
|
|
18371
|
+
},
|
|
18372
|
+
reopenButtonText: {
|
|
18373
|
+
fontSize: 13,
|
|
18374
|
+
fontWeight: "600",
|
|
18375
|
+
color: colors.orange
|
|
18376
|
+
},
|
|
18377
|
+
reopenForm: {
|
|
18378
|
+
backgroundColor: colors.card,
|
|
18379
|
+
borderWidth: 1,
|
|
18380
|
+
borderColor: colors.orange,
|
|
18381
|
+
borderRadius: 8,
|
|
18382
|
+
padding: 12,
|
|
18383
|
+
marginBottom: 12
|
|
18384
|
+
},
|
|
18385
|
+
reopenFormTitle: {
|
|
18386
|
+
fontSize: 13,
|
|
18387
|
+
fontWeight: "600",
|
|
18388
|
+
color: colors.orange,
|
|
18389
|
+
marginBottom: 8
|
|
18390
|
+
},
|
|
18391
|
+
reopenInput: {
|
|
18392
|
+
backgroundColor: colors.bg,
|
|
18393
|
+
borderWidth: 1,
|
|
18394
|
+
borderColor: colors.border,
|
|
18395
|
+
borderRadius: 6,
|
|
18396
|
+
padding: 8,
|
|
18397
|
+
color: colors.textPrimary,
|
|
18398
|
+
fontSize: 13,
|
|
18399
|
+
lineHeight: 18,
|
|
18400
|
+
minHeight: 60,
|
|
18401
|
+
textAlignVertical: "top"
|
|
18402
|
+
},
|
|
18403
|
+
reopenErrorText: {
|
|
18404
|
+
fontSize: 12,
|
|
18405
|
+
color: colors.red,
|
|
18406
|
+
marginTop: 6
|
|
18407
|
+
},
|
|
18408
|
+
reopenActions: {
|
|
18409
|
+
flexDirection: "row",
|
|
18410
|
+
gap: 8,
|
|
18411
|
+
marginTop: 8
|
|
18412
|
+
},
|
|
18413
|
+
reopenSubmitButton: {
|
|
18414
|
+
flex: 1,
|
|
18415
|
+
backgroundColor: colors.orange,
|
|
18416
|
+
borderRadius: 6,
|
|
18417
|
+
paddingVertical: 8,
|
|
18418
|
+
paddingHorizontal: 12,
|
|
18419
|
+
alignItems: "center",
|
|
18420
|
+
justifyContent: "center"
|
|
18421
|
+
},
|
|
18422
|
+
reopenSubmitDisabled: {
|
|
18423
|
+
backgroundColor: colors.card,
|
|
18424
|
+
opacity: 0.7
|
|
18425
|
+
},
|
|
18426
|
+
reopenSubmitText: {
|
|
18427
|
+
fontSize: 13,
|
|
18428
|
+
fontWeight: "600",
|
|
18429
|
+
color: "#fff"
|
|
18430
|
+
},
|
|
18431
|
+
reopenSubmitTextDisabled: {
|
|
18432
|
+
color: colors.textMuted
|
|
18433
|
+
},
|
|
18434
|
+
reopenCancelButton: {
|
|
18435
|
+
borderWidth: 1,
|
|
18436
|
+
borderColor: colors.border,
|
|
18437
|
+
borderRadius: 6,
|
|
18438
|
+
paddingVertical: 8,
|
|
18439
|
+
paddingHorizontal: 12,
|
|
18440
|
+
alignItems: "center"
|
|
18441
|
+
},
|
|
18442
|
+
reopenCancelText: {
|
|
18443
|
+
fontSize: 13,
|
|
18444
|
+
color: colors.textSecondary
|
|
18445
|
+
},
|
|
17817
18446
|
screenshotSection: {
|
|
17818
18447
|
marginBottom: 12
|
|
17819
18448
|
},
|
|
@@ -17872,11 +18501,13 @@ function SessionStartScreen({ nav }) {
|
|
|
17872
18501
|
const styles5 = (0, import_react20.useMemo)(() => createStyles13(), [widgetColorScheme]);
|
|
17873
18502
|
const [focusArea, setFocusArea] = (0, import_react20.useState)("");
|
|
17874
18503
|
const [isStarting, setIsStarting] = (0, import_react20.useState)(false);
|
|
18504
|
+
const [error, setError] = (0, import_react20.useState)(null);
|
|
17875
18505
|
const trackNames = Array.from(new Set(
|
|
17876
18506
|
assignments.filter((a) => a.testCase.track?.name).map((a) => a.testCase.track.name)
|
|
17877
18507
|
)).slice(0, 6);
|
|
17878
18508
|
const handleStart = async () => {
|
|
17879
18509
|
if (isStarting) return;
|
|
18510
|
+
setError(null);
|
|
17880
18511
|
import_react_native19.Keyboard.dismiss();
|
|
17881
18512
|
setIsStarting(true);
|
|
17882
18513
|
try {
|
|
@@ -17885,6 +18516,10 @@ function SessionStartScreen({ nav }) {
|
|
|
17885
18516
|
});
|
|
17886
18517
|
if (result.success) {
|
|
17887
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);
|
|
17888
18523
|
}
|
|
17889
18524
|
} finally {
|
|
17890
18525
|
setIsStarting(false);
|
|
@@ -17910,7 +18545,7 @@ function SessionStartScreen({ nav }) {
|
|
|
17910
18545
|
activeOpacity: 0.7
|
|
17911
18546
|
},
|
|
17912
18547
|
/* @__PURE__ */ import_react20.default.createElement(import_react_native19.Text, { style: [styles5.chipText, focusArea === name && styles5.chipTextActive] }, name)
|
|
17913
|
-
)))), /* @__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(
|
|
17914
18549
|
import_react_native19.TouchableOpacity,
|
|
17915
18550
|
{
|
|
17916
18551
|
style: [styles5.startBtn, isStarting && styles5.startBtnDisabled],
|
|
@@ -17989,6 +18624,20 @@ function createStyles13() {
|
|
|
17989
18624
|
chipTextActive: {
|
|
17990
18625
|
color: colors.blueLight
|
|
17991
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
|
+
},
|
|
17992
18641
|
startBtn: {
|
|
17993
18642
|
paddingVertical: 14,
|
|
17994
18643
|
backgroundColor: colors.blueSurface,
|