@bbearai/react-native 0.8.1 → 0.8.3
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 +372 -36
- package/dist/index.mjs +395 -59
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -79,6 +79,11 @@ interface BugBearContextValue {
|
|
|
79
79
|
refreshTesterInfo: () => Promise<void>;
|
|
80
80
|
issueCounts: IssueCounts;
|
|
81
81
|
refreshIssueCounts: () => Promise<void>;
|
|
82
|
+
/** Reopen a done issue with a required reason */
|
|
83
|
+
reopenReport: (reportId: string, reason: string) => Promise<{
|
|
84
|
+
success: boolean;
|
|
85
|
+
error?: string;
|
|
86
|
+
}>;
|
|
82
87
|
/** Number of queued offline operations waiting to be sent. */
|
|
83
88
|
queuedCount: number;
|
|
84
89
|
/** URL to the BugBear web dashboard (for linking testers to the full web experience) */
|
package/dist/index.d.ts
CHANGED
|
@@ -79,6 +79,11 @@ interface BugBearContextValue {
|
|
|
79
79
|
refreshTesterInfo: () => Promise<void>;
|
|
80
80
|
issueCounts: IssueCounts;
|
|
81
81
|
refreshIssueCounts: () => Promise<void>;
|
|
82
|
+
/** Reopen a done issue with a required reason */
|
|
83
|
+
reopenReport: (reportId: string, reason: string) => Promise<{
|
|
84
|
+
success: boolean;
|
|
85
|
+
error?: string;
|
|
86
|
+
}>;
|
|
82
87
|
/** Number of queued offline operations waiting to be sent. */
|
|
83
88
|
queuedCount: number;
|
|
84
89
|
/** URL to the BugBear web dashboard (for linking testers to the full web experience) */
|
package/dist/index.js
CHANGED
|
@@ -12230,6 +12230,7 @@ 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;
|
|
@@ -12307,6 +12308,10 @@ var BugBearClient = class {
|
|
|
12307
12308
|
this.initOfflineQueue();
|
|
12308
12309
|
this.initMonitoring();
|
|
12309
12310
|
}
|
|
12311
|
+
/** Cache key scoped to the active project. */
|
|
12312
|
+
get sessionCacheKey() {
|
|
12313
|
+
return `bugbear_session_${this.config.projectId ?? "unknown"}`;
|
|
12314
|
+
}
|
|
12310
12315
|
/** Initialize offline queue if configured. Shared by both init paths. */
|
|
12311
12316
|
initOfflineQueue() {
|
|
12312
12317
|
if (this.config.offlineQueue?.enabled) {
|
|
@@ -12370,6 +12375,26 @@ var BugBearClient = class {
|
|
|
12370
12375
|
await this.pendingInit;
|
|
12371
12376
|
if (this.initError) throw this.initError;
|
|
12372
12377
|
}
|
|
12378
|
+
/**
|
|
12379
|
+
* Fire-and-forget call to a dashboard notification endpoint.
|
|
12380
|
+
* Only works when apiKey is configured (needed for API auth).
|
|
12381
|
+
* Failures are silently ignored — notifications are best-effort.
|
|
12382
|
+
*/
|
|
12383
|
+
async notifyDashboard(path, body) {
|
|
12384
|
+
if (!this.config.apiKey) return;
|
|
12385
|
+
try {
|
|
12386
|
+
const baseUrl = (this.config.apiBaseUrl || DEFAULT_API_BASE_URL).replace(/\/$/, "");
|
|
12387
|
+
await fetch(`${baseUrl}/api/v1/notifications/${path}`, {
|
|
12388
|
+
method: "POST",
|
|
12389
|
+
headers: {
|
|
12390
|
+
"Content-Type": "application/json",
|
|
12391
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
12392
|
+
},
|
|
12393
|
+
body: JSON.stringify(body)
|
|
12394
|
+
});
|
|
12395
|
+
} catch {
|
|
12396
|
+
}
|
|
12397
|
+
}
|
|
12373
12398
|
// ── Offline Queue ─────────────────────────────────────────
|
|
12374
12399
|
/**
|
|
12375
12400
|
* Access the offline queue (if enabled).
|
|
@@ -12396,6 +12421,48 @@ var BugBearClient = class {
|
|
|
12396
12421
|
}
|
|
12397
12422
|
await this._queue.load();
|
|
12398
12423
|
}
|
|
12424
|
+
// ── Session Cache ──────────────────────────────────────────
|
|
12425
|
+
/**
|
|
12426
|
+
* Swap the session cache storage adapter (for React Native — pass AsyncStorage).
|
|
12427
|
+
* Must be called before getCachedSession() for persistence across app kills.
|
|
12428
|
+
* Web callers don't need this — LocalStorageAdapter is the default.
|
|
12429
|
+
*/
|
|
12430
|
+
setSessionStorage(adapter) {
|
|
12431
|
+
this._sessionStorage = adapter;
|
|
12432
|
+
}
|
|
12433
|
+
/**
|
|
12434
|
+
* Cache the active QA session locally for instant restore on app restart.
|
|
12435
|
+
* Pass null to clear the cache (e.g. after ending a session).
|
|
12436
|
+
*/
|
|
12437
|
+
async cacheSession(session) {
|
|
12438
|
+
try {
|
|
12439
|
+
if (session) {
|
|
12440
|
+
await this._sessionStorage.setItem(this.sessionCacheKey, JSON.stringify(session));
|
|
12441
|
+
} else {
|
|
12442
|
+
await this._sessionStorage.removeItem(this.sessionCacheKey);
|
|
12443
|
+
}
|
|
12444
|
+
} catch {
|
|
12445
|
+
}
|
|
12446
|
+
}
|
|
12447
|
+
/**
|
|
12448
|
+
* Retrieve the cached QA session. Returns null if no cache, if stale (>24h),
|
|
12449
|
+
* or if parsing fails. The DB fetch in initializeBugBear() is the source of truth.
|
|
12450
|
+
*/
|
|
12451
|
+
async getCachedSession() {
|
|
12452
|
+
try {
|
|
12453
|
+
const raw = await this._sessionStorage.getItem(this.sessionCacheKey);
|
|
12454
|
+
if (!raw) return null;
|
|
12455
|
+
const session = JSON.parse(raw);
|
|
12456
|
+
const age = Date.now() - new Date(session.startedAt).getTime();
|
|
12457
|
+
if (age > 24 * 60 * 60 * 1e3) {
|
|
12458
|
+
await this._sessionStorage.removeItem(this.sessionCacheKey);
|
|
12459
|
+
return null;
|
|
12460
|
+
}
|
|
12461
|
+
return session;
|
|
12462
|
+
} catch {
|
|
12463
|
+
return null;
|
|
12464
|
+
}
|
|
12465
|
+
}
|
|
12399
12466
|
registerQueueHandlers() {
|
|
12400
12467
|
if (!this._queue) return;
|
|
12401
12468
|
this._queue.registerHandler("report", async (payload) => {
|
|
@@ -12413,6 +12480,11 @@ var BugBearClient = class {
|
|
|
12413
12480
|
if (error) return { success: false, error: error.message };
|
|
12414
12481
|
return { success: true };
|
|
12415
12482
|
});
|
|
12483
|
+
this._queue.registerHandler("email_capture", async (payload) => {
|
|
12484
|
+
const { error } = await this.supabase.from("email_captures").insert(payload).select("id").single();
|
|
12485
|
+
if (error) return { success: false, error: error.message };
|
|
12486
|
+
return { success: true };
|
|
12487
|
+
});
|
|
12416
12488
|
}
|
|
12417
12489
|
// ── Realtime Subscriptions ─────────────────────────────────
|
|
12418
12490
|
/** Whether realtime is enabled in config. */
|
|
@@ -12600,6 +12672,8 @@ var BugBearClient = class {
|
|
|
12600
12672
|
if (this.config.onReportSubmitted) {
|
|
12601
12673
|
this.config.onReportSubmitted(report);
|
|
12602
12674
|
}
|
|
12675
|
+
this.notifyDashboard("report", { reportId: data.id }).catch(() => {
|
|
12676
|
+
});
|
|
12603
12677
|
return { success: true, reportId: data.id };
|
|
12604
12678
|
} catch (err) {
|
|
12605
12679
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -12612,6 +12686,44 @@ var BugBearClient = class {
|
|
|
12612
12686
|
this.reportSubmitInFlight = false;
|
|
12613
12687
|
}
|
|
12614
12688
|
}
|
|
12689
|
+
/**
|
|
12690
|
+
* Capture an email for QA testing.
|
|
12691
|
+
* Called by the email interceptor — not typically called directly.
|
|
12692
|
+
*/
|
|
12693
|
+
async captureEmail(payload) {
|
|
12694
|
+
try {
|
|
12695
|
+
await this.ready();
|
|
12696
|
+
if (!payload.subject || !payload.to || payload.to.length === 0) {
|
|
12697
|
+
return { success: false, error: "subject and to are required" };
|
|
12698
|
+
}
|
|
12699
|
+
const record = {
|
|
12700
|
+
project_id: this.config.projectId,
|
|
12701
|
+
to_addresses: payload.to,
|
|
12702
|
+
from_address: payload.from || null,
|
|
12703
|
+
subject: payload.subject,
|
|
12704
|
+
html_content: payload.html || null,
|
|
12705
|
+
text_content: payload.text || null,
|
|
12706
|
+
template_id: payload.templateId || null,
|
|
12707
|
+
metadata: payload.metadata || {},
|
|
12708
|
+
capture_mode: payload.captureMode,
|
|
12709
|
+
was_delivered: payload.wasDelivered,
|
|
12710
|
+
delivery_status: payload.wasDelivered ? "sent" : "pending"
|
|
12711
|
+
};
|
|
12712
|
+
const { data, error } = await this.supabase.from("email_captures").insert(record).select("id").single();
|
|
12713
|
+
if (error) {
|
|
12714
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
12715
|
+
await this._queue.enqueue("email_capture", record);
|
|
12716
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
12717
|
+
}
|
|
12718
|
+
console.error("BugBear: Failed to capture email", error.message);
|
|
12719
|
+
return { success: false, error: error.message };
|
|
12720
|
+
}
|
|
12721
|
+
return { success: true, captureId: data.id };
|
|
12722
|
+
} catch (err) {
|
|
12723
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
12724
|
+
return { success: false, error: message };
|
|
12725
|
+
}
|
|
12726
|
+
}
|
|
12615
12727
|
/**
|
|
12616
12728
|
* Get assigned tests for current user
|
|
12617
12729
|
* First looks up the tester by email, then fetches their assignments
|
|
@@ -13233,6 +13345,32 @@ var BugBearClient = class {
|
|
|
13233
13345
|
return [];
|
|
13234
13346
|
}
|
|
13235
13347
|
}
|
|
13348
|
+
/**
|
|
13349
|
+
* Reopen a done issue that the tester believes isn't actually fixed.
|
|
13350
|
+
* Transitions the report from a done status back to 'confirmed'.
|
|
13351
|
+
*/
|
|
13352
|
+
async reopenReport(reportId, reason) {
|
|
13353
|
+
try {
|
|
13354
|
+
const testerInfo = await this.getTesterInfo();
|
|
13355
|
+
if (!testerInfo) return { success: false, error: "Not authenticated as tester" };
|
|
13356
|
+
const { data, error } = await this.supabase.rpc("reopen_report", {
|
|
13357
|
+
p_report_id: reportId,
|
|
13358
|
+
p_tester_id: testerInfo.id,
|
|
13359
|
+
p_reason: reason
|
|
13360
|
+
});
|
|
13361
|
+
if (error) {
|
|
13362
|
+
console.error("BugBear: Failed to reopen report", formatPgError(error));
|
|
13363
|
+
return { success: false, error: error.message };
|
|
13364
|
+
}
|
|
13365
|
+
if (!data?.success) {
|
|
13366
|
+
return { success: false, error: data?.error || "Failed to reopen report" };
|
|
13367
|
+
}
|
|
13368
|
+
return { success: true };
|
|
13369
|
+
} catch (err) {
|
|
13370
|
+
console.error("BugBear: Error reopening report", err);
|
|
13371
|
+
return { success: false, error: "Unexpected error" };
|
|
13372
|
+
}
|
|
13373
|
+
}
|
|
13236
13374
|
/**
|
|
13237
13375
|
* Basic email format validation (defense in depth)
|
|
13238
13376
|
*/
|
|
@@ -13819,7 +13957,7 @@ var BugBearClient = class {
|
|
|
13819
13957
|
insertData.attachments = safeAttachments;
|
|
13820
13958
|
}
|
|
13821
13959
|
}
|
|
13822
|
-
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
13960
|
+
const { data: msgData, error } = await this.supabase.from("discussion_messages").insert(insertData).select("id").single();
|
|
13823
13961
|
if (error) {
|
|
13824
13962
|
if (this._queue && isNetworkError(error.message)) {
|
|
13825
13963
|
await this._queue.enqueue("message", insertData);
|
|
@@ -13828,6 +13966,10 @@ var BugBearClient = class {
|
|
|
13828
13966
|
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
13829
13967
|
return false;
|
|
13830
13968
|
}
|
|
13969
|
+
if (msgData?.id) {
|
|
13970
|
+
this.notifyDashboard("message", { threadId, messageId: msgData.id }).catch(() => {
|
|
13971
|
+
});
|
|
13972
|
+
}
|
|
13831
13973
|
await this.markThreadAsRead(threadId);
|
|
13832
13974
|
return true;
|
|
13833
13975
|
} catch (err) {
|
|
@@ -13953,6 +14095,7 @@ var BugBearClient = class {
|
|
|
13953
14095
|
if (!session) {
|
|
13954
14096
|
return { success: false, error: "Session created but could not be fetched" };
|
|
13955
14097
|
}
|
|
14098
|
+
await this.cacheSession(session);
|
|
13956
14099
|
return { success: true, session };
|
|
13957
14100
|
} catch (err) {
|
|
13958
14101
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -13976,6 +14119,7 @@ var BugBearClient = class {
|
|
|
13976
14119
|
return { success: false, error: error.message };
|
|
13977
14120
|
}
|
|
13978
14121
|
const session = this.transformSession(data);
|
|
14122
|
+
await this.cacheSession(null);
|
|
13979
14123
|
return { success: true, session };
|
|
13980
14124
|
} catch (err) {
|
|
13981
14125
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
@@ -13991,8 +14135,13 @@ var BugBearClient = class {
|
|
|
13991
14135
|
const testerInfo = await this.getTesterInfo();
|
|
13992
14136
|
if (!testerInfo) return null;
|
|
13993
14137
|
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
|
-
|
|
14138
|
+
if (error || !data) {
|
|
14139
|
+
await this.cacheSession(null);
|
|
14140
|
+
return null;
|
|
14141
|
+
}
|
|
14142
|
+
const session = this.transformSession(data);
|
|
14143
|
+
await this.cacheSession(session);
|
|
14144
|
+
return session;
|
|
13996
14145
|
} catch (err) {
|
|
13997
14146
|
console.error("BugBear: Error fetching active session", err);
|
|
13998
14147
|
return null;
|
|
@@ -14510,6 +14659,7 @@ var BugBearContext = (0, import_react.createContext)({
|
|
|
14510
14659
|
issueCounts: { open: 0, done: 0, reopened: 0 },
|
|
14511
14660
|
refreshIssueCounts: async () => {
|
|
14512
14661
|
},
|
|
14662
|
+
reopenReport: async () => ({ success: false }),
|
|
14513
14663
|
queuedCount: 0,
|
|
14514
14664
|
dashboardUrl: void 0,
|
|
14515
14665
|
onError: void 0
|
|
@@ -14651,6 +14801,14 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14651
14801
|
const counts = await client.getIssueCounts();
|
|
14652
14802
|
setIssueCounts(counts);
|
|
14653
14803
|
}, [client]);
|
|
14804
|
+
const reopenReport = (0, import_react.useCallback)(async (reportId, reason) => {
|
|
14805
|
+
if (!client) return { success: false, error: "Client not initialized" };
|
|
14806
|
+
const result = await client.reopenReport(reportId, reason);
|
|
14807
|
+
if (result.success) {
|
|
14808
|
+
await refreshIssueCounts();
|
|
14809
|
+
}
|
|
14810
|
+
return result;
|
|
14811
|
+
}, [client, refreshIssueCounts]);
|
|
14654
14812
|
const initializeBugBear = (0, import_react.useCallback)(async (bugBearClient) => {
|
|
14655
14813
|
setIsLoading(true);
|
|
14656
14814
|
try {
|
|
@@ -14736,6 +14894,10 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14736
14894
|
setClient(newClient);
|
|
14737
14895
|
(async () => {
|
|
14738
14896
|
try {
|
|
14897
|
+
const cachedSession = await newClient.getCachedSession();
|
|
14898
|
+
if (cachedSession) {
|
|
14899
|
+
setActiveSession(cachedSession);
|
|
14900
|
+
}
|
|
14739
14901
|
await initializeBugBear(newClient);
|
|
14740
14902
|
if (newClient.monitor && config.monitoring) {
|
|
14741
14903
|
const getCurrentRoute = () => contextCapture.getCurrentRoute();
|
|
@@ -14870,6 +15032,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
14870
15032
|
// Issue tracking
|
|
14871
15033
|
issueCounts,
|
|
14872
15034
|
refreshIssueCounts,
|
|
15035
|
+
reopenReport,
|
|
14873
15036
|
queuedCount,
|
|
14874
15037
|
dashboardUrl: config.dashboardUrl,
|
|
14875
15038
|
onError: config.onError
|
|
@@ -15601,6 +15764,17 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
15601
15764
|
setShowSteps(true);
|
|
15602
15765
|
setShowDetails(false);
|
|
15603
15766
|
}, [displayedAssignment?.id]);
|
|
15767
|
+
(0, import_react5.useEffect)(() => {
|
|
15768
|
+
if (!client || !displayedAssignment || displayedAssignment.status !== "pending") return;
|
|
15769
|
+
let cancelled = false;
|
|
15770
|
+
(async () => {
|
|
15771
|
+
await client.updateAssignmentStatus(displayedAssignment.id, "in_progress");
|
|
15772
|
+
if (!cancelled) await refreshAssignments();
|
|
15773
|
+
})();
|
|
15774
|
+
return () => {
|
|
15775
|
+
cancelled = true;
|
|
15776
|
+
};
|
|
15777
|
+
}, [client, displayedAssignment?.id, displayedAssignment?.status, refreshAssignments]);
|
|
15604
15778
|
(0, import_react5.useEffect)(() => {
|
|
15605
15779
|
const active = displayedAssignment?.status === "in_progress" ? displayedAssignment : null;
|
|
15606
15780
|
if (!active?.startedAt) {
|
|
@@ -15651,17 +15825,6 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
15651
15825
|
setIsSubmitting(false);
|
|
15652
15826
|
}
|
|
15653
15827
|
}, [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
15828
|
const handleReopen = (0, import_react5.useCallback)(async () => {
|
|
15666
15829
|
if (!client || !displayedAssignment || isSubmitting) return;
|
|
15667
15830
|
import_react_native5.Keyboard.dismiss();
|
|
@@ -15807,15 +15970,7 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
15807
15970
|
disabled: isSubmitting
|
|
15808
15971
|
},
|
|
15809
15972
|
/* @__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:"), [
|
|
15973
|
+
))) : /* @__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
15974
|
{ reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
|
|
15820
15975
|
{ reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
|
|
15821
15976
|
{ reason: "dependency", label: "\u{1F517} Needs another test first" },
|
|
@@ -15929,9 +16084,6 @@ function createStyles2() {
|
|
|
15929
16084
|
completedLabel: { fontSize: 13, fontWeight: "600", color: colors.textSecondary },
|
|
15930
16085
|
reopenBtn: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.blue },
|
|
15931
16086
|
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
16087
|
// Action buttons
|
|
15936
16088
|
actionButtons: { flexDirection: "row", gap: 10, marginTop: 8 },
|
|
15937
16089
|
actionBtn: { flex: 1, paddingVertical: 14, borderRadius: 12, alignItems: "center" },
|
|
@@ -16044,17 +16196,23 @@ function TestListScreen({ nav }) {
|
|
|
16044
16196
|
const keyMatch = a.testCase.testKey.toLowerCase().includes(q);
|
|
16045
16197
|
if (!titleMatch && !keyMatch) return false;
|
|
16046
16198
|
}
|
|
16047
|
-
if (filter === "
|
|
16048
|
-
|
|
16049
|
-
|
|
16199
|
+
if (filter === "todo") {
|
|
16200
|
+
return !a.isVerification && (a.status === "pending" || a.status === "in_progress" || a.status === "failed");
|
|
16201
|
+
}
|
|
16202
|
+
if (filter === "retest") {
|
|
16203
|
+
return !!a.isVerification && (a.status === "pending" || a.status === "in_progress");
|
|
16204
|
+
}
|
|
16205
|
+
if (filter === "done") {
|
|
16206
|
+
return a.status === "passed" || a.status === "skipped" || a.status === "blocked";
|
|
16207
|
+
}
|
|
16050
16208
|
return true;
|
|
16051
16209
|
}, [platformFilter, roleFilter, trackFilter, searchQuery, filter]);
|
|
16052
16210
|
if (isLoading) return /* @__PURE__ */ import_react6.default.createElement(TestListScreenSkeleton, null);
|
|
16053
16211
|
return /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, null, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles5.filterBar }, [
|
|
16054
16212
|
{ key: "all", label: "All", count: assignments.length },
|
|
16055
|
-
{ key: "
|
|
16056
|
-
{ key: "
|
|
16057
|
-
{ key: "
|
|
16213
|
+
{ key: "todo", label: "To Do", count: assignments.filter((a) => !a.isVerification && (a.status === "pending" || a.status === "in_progress" || a.status === "failed")).length },
|
|
16214
|
+
{ key: "retest", label: "Retest", count: assignments.filter((a) => !!a.isVerification && (a.status === "pending" || a.status === "in_progress")).length },
|
|
16215
|
+
{ key: "done", label: "Done", count: assignments.filter((a) => a.status === "passed" || a.status === "skipped" || a.status === "blocked").length }
|
|
16058
16216
|
].map((f) => /* @__PURE__ */ import_react6.default.createElement(import_react_native6.TouchableOpacity, { key: f.key, style: [styles5.filterBtn, filter === f.key && styles5.filterBtnActive], onPress: () => setFilter(f.key) }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles5.filterBtnText, filter === f.key && styles5.filterBtnTextActive] }, f.label, " (", f.count, ")")))), availableRoles.length >= 2 && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles5.roleSection }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles5.roleBar }, /* @__PURE__ */ import_react6.default.createElement(
|
|
16059
16217
|
import_react_native6.TouchableOpacity,
|
|
16060
16218
|
{
|
|
@@ -16157,7 +16315,13 @@ function TestListScreen({ nav }) {
|
|
|
16157
16315
|
onPress: () => nav.push({ name: "TEST_DETAIL", testId: assignment.id })
|
|
16158
16316
|
},
|
|
16159
16317
|
/* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles5.testBadge }, badge.icon),
|
|
16160
|
-
/* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles5.testInfo }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles5.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles5.testMetaRow }, assignment.isVerification && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style:
|
|
16318
|
+
/* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles5.testInfo }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles5.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles5.testMetaRow }, assignment.isVerification && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: [
|
|
16319
|
+
styles5.retestTag,
|
|
16320
|
+
assignment.status === "passed" && { backgroundColor: colors.greenDark, borderColor: colors.greenBorder }
|
|
16321
|
+
] }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [
|
|
16322
|
+
styles5.retestTagText,
|
|
16323
|
+
assignment.status === "passed" && { color: colors.greenLight }
|
|
16324
|
+
] }, assignment.status === "passed" ? "Verified" : "Retest")), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles5.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: styles5.roleBadgeRow }, /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: styles5.testMeta }, " \xB7 "), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: [styles5.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ import_react6.default.createElement(import_react_native6.Text, { style: [styles5.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name)))),
|
|
16161
16325
|
/* @__PURE__ */ import_react6.default.createElement(import_react_native6.View, { style: [
|
|
16162
16326
|
styles5.statusPill,
|
|
16163
16327
|
{
|
|
@@ -17673,9 +17837,15 @@ function createStyles11() {
|
|
|
17673
17837
|
// src/widget/screens/IssueDetailScreen.tsx
|
|
17674
17838
|
var import_react19 = __toESM(require("react"));
|
|
17675
17839
|
var import_react_native18 = require("react-native");
|
|
17840
|
+
var DONE_STATUSES = ["verified", "resolved", "closed", "reviewed"];
|
|
17676
17841
|
function IssueDetailScreen({ nav, issue }) {
|
|
17677
|
-
const { dashboardUrl, widgetColorScheme } = useBugBear();
|
|
17842
|
+
const { dashboardUrl, widgetColorScheme, reopenReport } = useBugBear();
|
|
17678
17843
|
const styles5 = (0, import_react19.useMemo)(() => createStyles12(), [widgetColorScheme]);
|
|
17844
|
+
const [showReopenForm, setShowReopenForm] = (0, import_react19.useState)(false);
|
|
17845
|
+
const [reopenReason, setReopenReason] = (0, import_react19.useState)("");
|
|
17846
|
+
const [isSubmitting, setIsSubmitting] = (0, import_react19.useState)(false);
|
|
17847
|
+
const [reopenError, setReopenError] = (0, import_react19.useState)(null);
|
|
17848
|
+
const [wasReopened, setWasReopened] = (0, import_react19.useState)(false);
|
|
17679
17849
|
const STATUS_LABELS = (0, import_react19.useMemo)(() => ({
|
|
17680
17850
|
new: { label: "New", bg: colors.blueDark, color: colors.blueLight },
|
|
17681
17851
|
triaging: { label: "Triaging", bg: colors.blueDark, color: colors.blueLight },
|
|
@@ -17698,7 +17868,68 @@ function IssueDetailScreen({ nav, issue }) {
|
|
|
17698
17868
|
}), [widgetColorScheme]);
|
|
17699
17869
|
const statusConfig = STATUS_LABELS[issue.status] || { label: issue.status, bg: colors.card, color: colors.textSecondary };
|
|
17700
17870
|
const severityConfig = issue.severity ? SEVERITY_CONFIG[issue.severity] : null;
|
|
17701
|
-
|
|
17871
|
+
const isDone = DONE_STATUSES.includes(issue.status);
|
|
17872
|
+
const handleReopen = (0, import_react19.useCallback)(async () => {
|
|
17873
|
+
if (!reopenReason.trim()) return;
|
|
17874
|
+
setIsSubmitting(true);
|
|
17875
|
+
setReopenError(null);
|
|
17876
|
+
const result = await reopenReport(issue.id, reopenReason.trim());
|
|
17877
|
+
setIsSubmitting(false);
|
|
17878
|
+
if (result.success) {
|
|
17879
|
+
setWasReopened(true);
|
|
17880
|
+
setShowReopenForm(false);
|
|
17881
|
+
} else {
|
|
17882
|
+
setReopenError(result.error || "Failed to reopen");
|
|
17883
|
+
}
|
|
17884
|
+
}, [reopenReason, reopenReport, issue.id]);
|
|
17885
|
+
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(
|
|
17886
|
+
import_react_native18.TouchableOpacity,
|
|
17887
|
+
{
|
|
17888
|
+
style: styles5.reopenButton,
|
|
17889
|
+
onPress: () => setShowReopenForm(true),
|
|
17890
|
+
activeOpacity: 0.7
|
|
17891
|
+
},
|
|
17892
|
+
/* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.reopenButtonText }, "\u{1F504}", " Not Fixed \u2014 Reopen Issue")
|
|
17893
|
+
), 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(
|
|
17894
|
+
import_react_native18.TextInput,
|
|
17895
|
+
{
|
|
17896
|
+
value: reopenReason,
|
|
17897
|
+
onChangeText: setReopenReason,
|
|
17898
|
+
placeholder: "Describe what you're still seeing...",
|
|
17899
|
+
placeholderTextColor: colors.textMuted,
|
|
17900
|
+
style: styles5.reopenInput,
|
|
17901
|
+
multiline: true,
|
|
17902
|
+
autoFocus: true
|
|
17903
|
+
}
|
|
17904
|
+
), 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(
|
|
17905
|
+
import_react_native18.TouchableOpacity,
|
|
17906
|
+
{
|
|
17907
|
+
style: [
|
|
17908
|
+
styles5.reopenSubmitButton,
|
|
17909
|
+
(!reopenReason.trim() || isSubmitting) && styles5.reopenSubmitDisabled
|
|
17910
|
+
],
|
|
17911
|
+
onPress: handleReopen,
|
|
17912
|
+
disabled: isSubmitting || !reopenReason.trim(),
|
|
17913
|
+
activeOpacity: 0.7
|
|
17914
|
+
},
|
|
17915
|
+
isSubmitting ? /* @__PURE__ */ import_react19.default.createElement(import_react_native18.ActivityIndicator, { size: "small", color: "#fff" }) : /* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: [
|
|
17916
|
+
styles5.reopenSubmitText,
|
|
17917
|
+
!reopenReason.trim() && styles5.reopenSubmitTextDisabled
|
|
17918
|
+
] }, "Reopen Issue")
|
|
17919
|
+
), /* @__PURE__ */ import_react19.default.createElement(
|
|
17920
|
+
import_react_native18.TouchableOpacity,
|
|
17921
|
+
{
|
|
17922
|
+
style: styles5.reopenCancelButton,
|
|
17923
|
+
onPress: () => {
|
|
17924
|
+
setShowReopenForm(false);
|
|
17925
|
+
setReopenReason("");
|
|
17926
|
+
setReopenError(null);
|
|
17927
|
+
},
|
|
17928
|
+
disabled: isSubmitting,
|
|
17929
|
+
activeOpacity: 0.7
|
|
17930
|
+
},
|
|
17931
|
+
/* @__PURE__ */ import_react19.default.createElement(import_react_native18.Text, { style: styles5.reopenCancelText }, "Cancel")
|
|
17932
|
+
))), 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(
|
|
17702
17933
|
import_react_native18.TouchableOpacity,
|
|
17703
17934
|
{
|
|
17704
17935
|
style: styles5.dashboardLink,
|
|
@@ -17750,6 +17981,28 @@ function createStyles12() {
|
|
|
17750
17981
|
color: colors.textSecondary,
|
|
17751
17982
|
lineHeight: 19
|
|
17752
17983
|
},
|
|
17984
|
+
reopenedCard: {
|
|
17985
|
+
backgroundColor: colors.yellowDark,
|
|
17986
|
+
borderWidth: 1,
|
|
17987
|
+
borderColor: colors.yellowBorder,
|
|
17988
|
+
borderRadius: 8,
|
|
17989
|
+
padding: 12,
|
|
17990
|
+
marginBottom: 12
|
|
17991
|
+
},
|
|
17992
|
+
reopenedRow: {
|
|
17993
|
+
flexDirection: "row",
|
|
17994
|
+
alignItems: "center",
|
|
17995
|
+
gap: 8
|
|
17996
|
+
},
|
|
17997
|
+
reopenedIcon: {
|
|
17998
|
+
fontSize: 14
|
|
17999
|
+
},
|
|
18000
|
+
reopenedText: {
|
|
18001
|
+
fontSize: 13,
|
|
18002
|
+
fontWeight: "600",
|
|
18003
|
+
color: colors.yellowLight,
|
|
18004
|
+
flex: 1
|
|
18005
|
+
},
|
|
17753
18006
|
verifiedCard: {
|
|
17754
18007
|
backgroundColor: colors.greenDark,
|
|
17755
18008
|
borderWidth: 1,
|
|
@@ -17793,7 +18046,7 @@ function createStyles12() {
|
|
|
17793
18046
|
originalBugIcon: {
|
|
17794
18047
|
fontSize: 16
|
|
17795
18048
|
},
|
|
17796
|
-
|
|
18049
|
+
originalBugTitleText: {
|
|
17797
18050
|
fontSize: 13,
|
|
17798
18051
|
fontWeight: "600",
|
|
17799
18052
|
color: colors.yellowLight
|
|
@@ -17802,6 +18055,89 @@ function createStyles12() {
|
|
|
17802
18055
|
fontSize: 12,
|
|
17803
18056
|
color: colors.yellowSubtle
|
|
17804
18057
|
},
|
|
18058
|
+
reopenButton: {
|
|
18059
|
+
borderWidth: 1,
|
|
18060
|
+
borderColor: colors.orange,
|
|
18061
|
+
borderRadius: 8,
|
|
18062
|
+
paddingVertical: 10,
|
|
18063
|
+
paddingHorizontal: 16,
|
|
18064
|
+
alignItems: "center",
|
|
18065
|
+
marginBottom: 12
|
|
18066
|
+
},
|
|
18067
|
+
reopenButtonText: {
|
|
18068
|
+
fontSize: 13,
|
|
18069
|
+
fontWeight: "600",
|
|
18070
|
+
color: colors.orange
|
|
18071
|
+
},
|
|
18072
|
+
reopenForm: {
|
|
18073
|
+
backgroundColor: colors.card,
|
|
18074
|
+
borderWidth: 1,
|
|
18075
|
+
borderColor: colors.orange,
|
|
18076
|
+
borderRadius: 8,
|
|
18077
|
+
padding: 12,
|
|
18078
|
+
marginBottom: 12
|
|
18079
|
+
},
|
|
18080
|
+
reopenFormTitle: {
|
|
18081
|
+
fontSize: 13,
|
|
18082
|
+
fontWeight: "600",
|
|
18083
|
+
color: colors.orange,
|
|
18084
|
+
marginBottom: 8
|
|
18085
|
+
},
|
|
18086
|
+
reopenInput: {
|
|
18087
|
+
backgroundColor: colors.bg,
|
|
18088
|
+
borderWidth: 1,
|
|
18089
|
+
borderColor: colors.border,
|
|
18090
|
+
borderRadius: 6,
|
|
18091
|
+
padding: 8,
|
|
18092
|
+
color: colors.textPrimary,
|
|
18093
|
+
fontSize: 13,
|
|
18094
|
+
lineHeight: 18,
|
|
18095
|
+
minHeight: 60,
|
|
18096
|
+
textAlignVertical: "top"
|
|
18097
|
+
},
|
|
18098
|
+
reopenErrorText: {
|
|
18099
|
+
fontSize: 12,
|
|
18100
|
+
color: colors.red,
|
|
18101
|
+
marginTop: 6
|
|
18102
|
+
},
|
|
18103
|
+
reopenActions: {
|
|
18104
|
+
flexDirection: "row",
|
|
18105
|
+
gap: 8,
|
|
18106
|
+
marginTop: 8
|
|
18107
|
+
},
|
|
18108
|
+
reopenSubmitButton: {
|
|
18109
|
+
flex: 1,
|
|
18110
|
+
backgroundColor: colors.orange,
|
|
18111
|
+
borderRadius: 6,
|
|
18112
|
+
paddingVertical: 8,
|
|
18113
|
+
paddingHorizontal: 12,
|
|
18114
|
+
alignItems: "center",
|
|
18115
|
+
justifyContent: "center"
|
|
18116
|
+
},
|
|
18117
|
+
reopenSubmitDisabled: {
|
|
18118
|
+
backgroundColor: colors.card,
|
|
18119
|
+
opacity: 0.7
|
|
18120
|
+
},
|
|
18121
|
+
reopenSubmitText: {
|
|
18122
|
+
fontSize: 13,
|
|
18123
|
+
fontWeight: "600",
|
|
18124
|
+
color: "#fff"
|
|
18125
|
+
},
|
|
18126
|
+
reopenSubmitTextDisabled: {
|
|
18127
|
+
color: colors.textMuted
|
|
18128
|
+
},
|
|
18129
|
+
reopenCancelButton: {
|
|
18130
|
+
borderWidth: 1,
|
|
18131
|
+
borderColor: colors.border,
|
|
18132
|
+
borderRadius: 6,
|
|
18133
|
+
paddingVertical: 8,
|
|
18134
|
+
paddingHorizontal: 12,
|
|
18135
|
+
alignItems: "center"
|
|
18136
|
+
},
|
|
18137
|
+
reopenCancelText: {
|
|
18138
|
+
fontSize: 13,
|
|
18139
|
+
color: colors.textSecondary
|
|
18140
|
+
},
|
|
17805
18141
|
screenshotSection: {
|
|
17806
18142
|
marginBottom: 12
|
|
17807
18143
|
},
|