@bbearai/react-native 0.5.6 → 0.6.0
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 +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +962 -431
- package/dist/index.mjs +849 -318
- package/package.json +12 -3
package/dist/index.mjs
CHANGED
|
@@ -9,14 +9,14 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
9
9
|
import React, { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
|
|
10
10
|
|
|
11
11
|
// ../../node_modules/tslib/tslib.es6.mjs
|
|
12
|
-
function __rest(
|
|
12
|
+
function __rest(s2, e) {
|
|
13
13
|
var t = {};
|
|
14
|
-
for (var p in
|
|
15
|
-
t[p] =
|
|
16
|
-
if (
|
|
17
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(
|
|
18
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(
|
|
19
|
-
t[p[i]] =
|
|
14
|
+
for (var p in s2) if (Object.prototype.hasOwnProperty.call(s2, p) && e.indexOf(p) < 0)
|
|
15
|
+
t[p] = s2[p];
|
|
16
|
+
if (s2 != null && typeof Object.getOwnPropertySymbols === "function")
|
|
17
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s2); i < p.length; i++) {
|
|
18
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s2, p[i]))
|
|
19
|
+
t[p[i]] = s2[p[i]];
|
|
20
20
|
}
|
|
21
21
|
return t;
|
|
22
22
|
}
|
|
@@ -831,9 +831,9 @@ var PostgrestFilterBuilder = class extends PostgrestTransformBuilder {
|
|
|
831
831
|
* @param values - The values array to filter with
|
|
832
832
|
*/
|
|
833
833
|
in(column, values) {
|
|
834
|
-
const cleanedValues = Array.from(new Set(values)).map((
|
|
835
|
-
if (typeof
|
|
836
|
-
else return `${
|
|
834
|
+
const cleanedValues = Array.from(new Set(values)).map((s2) => {
|
|
835
|
+
if (typeof s2 === "string" && PostgrestReservedCharsRegexp.test(s2)) return `"${s2}"`;
|
|
836
|
+
else return `${s2}`;
|
|
837
837
|
}).join(",");
|
|
838
838
|
this.url.searchParams.append(column, `in.(${cleanedValues})`);
|
|
839
839
|
return this;
|
|
@@ -845,9 +845,9 @@ var PostgrestFilterBuilder = class extends PostgrestTransformBuilder {
|
|
|
845
845
|
* @param values - The values array to filter with
|
|
846
846
|
*/
|
|
847
847
|
notIn(column, values) {
|
|
848
|
-
const cleanedValues = Array.from(new Set(values)).map((
|
|
849
|
-
if (typeof
|
|
850
|
-
else return `${
|
|
848
|
+
const cleanedValues = Array.from(new Set(values)).map((s2) => {
|
|
849
|
+
if (typeof s2 === "string" && PostgrestReservedCharsRegexp.test(s2)) return `"${s2}"`;
|
|
850
|
+
else return `${s2}`;
|
|
851
851
|
}).join(",");
|
|
852
852
|
this.url.searchParams.append(column, `not.in.(${cleanedValues})`);
|
|
853
853
|
return this;
|
|
@@ -11602,31 +11602,306 @@ function captureError(error, errorInfo) {
|
|
|
11602
11602
|
componentStack: errorInfo?.componentStack
|
|
11603
11603
|
};
|
|
11604
11604
|
}
|
|
11605
|
+
var LocalStorageAdapter = class {
|
|
11606
|
+
constructor() {
|
|
11607
|
+
this.fallback = /* @__PURE__ */ new Map();
|
|
11608
|
+
}
|
|
11609
|
+
get isAvailable() {
|
|
11610
|
+
try {
|
|
11611
|
+
const key = "__bugbear_test__";
|
|
11612
|
+
localStorage.setItem(key, "1");
|
|
11613
|
+
localStorage.removeItem(key);
|
|
11614
|
+
return true;
|
|
11615
|
+
} catch {
|
|
11616
|
+
return false;
|
|
11617
|
+
}
|
|
11618
|
+
}
|
|
11619
|
+
async getItem(key) {
|
|
11620
|
+
if (this.isAvailable) return localStorage.getItem(key);
|
|
11621
|
+
return this.fallback.get(key) ?? null;
|
|
11622
|
+
}
|
|
11623
|
+
async setItem(key, value) {
|
|
11624
|
+
if (this.isAvailable) {
|
|
11625
|
+
localStorage.setItem(key, value);
|
|
11626
|
+
} else {
|
|
11627
|
+
this.fallback.set(key, value);
|
|
11628
|
+
}
|
|
11629
|
+
}
|
|
11630
|
+
async removeItem(key) {
|
|
11631
|
+
if (this.isAvailable) {
|
|
11632
|
+
localStorage.removeItem(key);
|
|
11633
|
+
} else {
|
|
11634
|
+
this.fallback.delete(key);
|
|
11635
|
+
}
|
|
11636
|
+
}
|
|
11637
|
+
};
|
|
11638
|
+
var OfflineQueue = class {
|
|
11639
|
+
constructor(config) {
|
|
11640
|
+
this.items = [];
|
|
11641
|
+
this.storageKey = "bugbear_offline_queue";
|
|
11642
|
+
this.flushing = false;
|
|
11643
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
11644
|
+
this.maxItems = config.maxItems ?? 50;
|
|
11645
|
+
this.maxRetries = config.maxRetries ?? 5;
|
|
11646
|
+
this.storage = config.storage ?? new LocalStorageAdapter();
|
|
11647
|
+
}
|
|
11648
|
+
// ── Flush handler registration ──────────────────────────────
|
|
11649
|
+
/** Register a handler that replays a queued operation. */
|
|
11650
|
+
registerHandler(type, handler) {
|
|
11651
|
+
this.handlers.set(type, handler);
|
|
11652
|
+
}
|
|
11653
|
+
// ── Change listener ─────────────────────────────────────────
|
|
11654
|
+
/** Subscribe to queue count changes (for UI badges). */
|
|
11655
|
+
onChange(callback) {
|
|
11656
|
+
this.listener = callback;
|
|
11657
|
+
}
|
|
11658
|
+
notify() {
|
|
11659
|
+
this.listener?.(this.items.length);
|
|
11660
|
+
}
|
|
11661
|
+
// ── Persistence ─────────────────────────────────────────────
|
|
11662
|
+
/** Load queue from persistent storage. Call once after construction. */
|
|
11663
|
+
async load() {
|
|
11664
|
+
try {
|
|
11665
|
+
const raw = await this.storage.getItem(this.storageKey);
|
|
11666
|
+
if (raw) {
|
|
11667
|
+
this.items = JSON.parse(raw);
|
|
11668
|
+
}
|
|
11669
|
+
} catch {
|
|
11670
|
+
this.items = [];
|
|
11671
|
+
}
|
|
11672
|
+
this.notify();
|
|
11673
|
+
}
|
|
11674
|
+
async save() {
|
|
11675
|
+
try {
|
|
11676
|
+
await this.storage.setItem(this.storageKey, JSON.stringify(this.items));
|
|
11677
|
+
} catch (err) {
|
|
11678
|
+
console.error("BugBear: Failed to persist offline queue", err);
|
|
11679
|
+
}
|
|
11680
|
+
this.notify();
|
|
11681
|
+
}
|
|
11682
|
+
// ── Enqueue ─────────────────────────────────────────────────
|
|
11683
|
+
/** Add a failed operation to the queue. Returns the item ID. */
|
|
11684
|
+
async enqueue(type, payload) {
|
|
11685
|
+
if (this.items.length >= this.maxItems) {
|
|
11686
|
+
this.items.shift();
|
|
11687
|
+
}
|
|
11688
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
11689
|
+
this.items.push({ id, type, payload, createdAt: Date.now(), retries: 0 });
|
|
11690
|
+
await this.save();
|
|
11691
|
+
return id;
|
|
11692
|
+
}
|
|
11693
|
+
// ── Accessors ───────────────────────────────────────────────
|
|
11694
|
+
/** Number of items waiting to be flushed. */
|
|
11695
|
+
get count() {
|
|
11696
|
+
return this.items.length;
|
|
11697
|
+
}
|
|
11698
|
+
/** Read-only snapshot of pending items. */
|
|
11699
|
+
get pending() {
|
|
11700
|
+
return [...this.items];
|
|
11701
|
+
}
|
|
11702
|
+
/** Whether a flush is currently in progress. */
|
|
11703
|
+
get isFlushing() {
|
|
11704
|
+
return this.flushing;
|
|
11705
|
+
}
|
|
11706
|
+
// ── Flush ───────────────────────────────────────────────────
|
|
11707
|
+
/**
|
|
11708
|
+
* Process all queued items in FIFO order.
|
|
11709
|
+
* Stops early if a network error is encountered (still offline).
|
|
11710
|
+
*/
|
|
11711
|
+
async flush() {
|
|
11712
|
+
if (this.flushing || this.items.length === 0) {
|
|
11713
|
+
return { flushed: 0, failed: 0 };
|
|
11714
|
+
}
|
|
11715
|
+
this.flushing = true;
|
|
11716
|
+
let flushed = 0;
|
|
11717
|
+
let failed = 0;
|
|
11718
|
+
const snapshot = [...this.items];
|
|
11719
|
+
for (const item of snapshot) {
|
|
11720
|
+
const handler = this.handlers.get(item.type);
|
|
11721
|
+
if (!handler) {
|
|
11722
|
+
failed++;
|
|
11723
|
+
continue;
|
|
11724
|
+
}
|
|
11725
|
+
try {
|
|
11726
|
+
const result = await handler(item.payload);
|
|
11727
|
+
if (result.success) {
|
|
11728
|
+
this.items = this.items.filter((i) => i.id !== item.id);
|
|
11729
|
+
flushed++;
|
|
11730
|
+
} else if (isNetworkError(result.error)) {
|
|
11731
|
+
break;
|
|
11732
|
+
} else {
|
|
11733
|
+
const idx = this.items.findIndex((i) => i.id === item.id);
|
|
11734
|
+
if (idx !== -1) {
|
|
11735
|
+
this.items[idx].retries++;
|
|
11736
|
+
if (this.items[idx].retries >= this.maxRetries) {
|
|
11737
|
+
this.items.splice(idx, 1);
|
|
11738
|
+
}
|
|
11739
|
+
}
|
|
11740
|
+
failed++;
|
|
11741
|
+
}
|
|
11742
|
+
} catch {
|
|
11743
|
+
break;
|
|
11744
|
+
}
|
|
11745
|
+
}
|
|
11746
|
+
await this.save();
|
|
11747
|
+
this.flushing = false;
|
|
11748
|
+
return { flushed, failed };
|
|
11749
|
+
}
|
|
11750
|
+
// ── Clear ───────────────────────────────────────────────────
|
|
11751
|
+
/** Drop all queued items. */
|
|
11752
|
+
async clear() {
|
|
11753
|
+
this.items = [];
|
|
11754
|
+
await this.save();
|
|
11755
|
+
}
|
|
11756
|
+
};
|
|
11757
|
+
function isNetworkError(error) {
|
|
11758
|
+
if (!error) return false;
|
|
11759
|
+
const msg = error.toLowerCase();
|
|
11760
|
+
return msg.includes("failed to fetch") || msg.includes("networkerror") || msg.includes("network request failed") || msg.includes("timeout") || msg.includes("econnrefused") || msg.includes("enotfound") || msg.includes("load failed") || // Safari
|
|
11761
|
+
msg.includes("the internet connection appears to be offline") || msg.includes("a]server with the specified hostname could not be found");
|
|
11762
|
+
}
|
|
11605
11763
|
var formatPgError = (e) => {
|
|
11606
11764
|
if (!e || typeof e !== "object") return { raw: e };
|
|
11607
11765
|
const { message, code, details, hint } = e;
|
|
11608
11766
|
return { message, code, details, hint };
|
|
11609
11767
|
};
|
|
11610
|
-
var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
|
|
11611
|
-
var getEnvVar = (key) => {
|
|
11612
|
-
try {
|
|
11613
|
-
if (typeof process !== "undefined" && process.env) {
|
|
11614
|
-
return process.env[key];
|
|
11615
|
-
}
|
|
11616
|
-
} catch {
|
|
11617
|
-
}
|
|
11618
|
-
return void 0;
|
|
11619
|
-
};
|
|
11620
|
-
var HOSTED_BUGBEAR_ANON_KEY = getEnvVar("BUGBEAR_ANON_KEY") || getEnvVar("NEXT_PUBLIC_BUGBEAR_ANON_KEY") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt5eGd6am5xZ3ZhcHZsbnZxYXd6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNjgwNDIsImV4cCI6MjA4NDg0NDA0Mn0.NUkAlCHLFjeRoisbmNUVoGb4R6uQ8xs5LAEIX1BWTwU";
|
|
11621
11768
|
var BugBearClient = class {
|
|
11622
11769
|
constructor(config) {
|
|
11623
11770
|
this.navigationHistory = [];
|
|
11624
11771
|
this.reportSubmitInFlight = false;
|
|
11772
|
+
this._queue = null;
|
|
11773
|
+
this.realtimeChannels = [];
|
|
11774
|
+
if (!config.supabaseUrl) {
|
|
11775
|
+
throw new Error("BugBear: supabaseUrl is required. Get it from your BugBear project settings.");
|
|
11776
|
+
}
|
|
11777
|
+
if (!config.supabaseAnonKey) {
|
|
11778
|
+
throw new Error("BugBear: supabaseAnonKey is required. Get it from your BugBear project settings.");
|
|
11779
|
+
}
|
|
11625
11780
|
this.config = config;
|
|
11626
|
-
this.supabase = createClient(
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11781
|
+
this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
|
|
11782
|
+
if (config.offlineQueue?.enabled) {
|
|
11783
|
+
this._queue = new OfflineQueue({
|
|
11784
|
+
enabled: true,
|
|
11785
|
+
maxItems: config.offlineQueue.maxItems,
|
|
11786
|
+
maxRetries: config.offlineQueue.maxRetries
|
|
11787
|
+
});
|
|
11788
|
+
this.registerQueueHandlers();
|
|
11789
|
+
}
|
|
11790
|
+
}
|
|
11791
|
+
// ── Offline Queue ─────────────────────────────────────────
|
|
11792
|
+
/**
|
|
11793
|
+
* Access the offline queue (if enabled).
|
|
11794
|
+
* Use this to check queue.count, subscribe to changes, or trigger flush.
|
|
11795
|
+
*/
|
|
11796
|
+
get queue() {
|
|
11797
|
+
return this._queue;
|
|
11798
|
+
}
|
|
11799
|
+
/**
|
|
11800
|
+
* Initialize the offline queue with a platform-specific storage adapter.
|
|
11801
|
+
* Must be called after construction for React Native (which supplies AsyncStorage).
|
|
11802
|
+
* Web callers can skip this — LocalStorageAdapter is the default.
|
|
11803
|
+
*/
|
|
11804
|
+
async initQueue(storage) {
|
|
11805
|
+
if (!this._queue) return;
|
|
11806
|
+
if (storage) {
|
|
11807
|
+
this._queue = new OfflineQueue({
|
|
11808
|
+
enabled: true,
|
|
11809
|
+
maxItems: this.config.offlineQueue?.maxItems,
|
|
11810
|
+
maxRetries: this.config.offlineQueue?.maxRetries,
|
|
11811
|
+
storage
|
|
11812
|
+
});
|
|
11813
|
+
this.registerQueueHandlers();
|
|
11814
|
+
}
|
|
11815
|
+
await this._queue.load();
|
|
11816
|
+
}
|
|
11817
|
+
registerQueueHandlers() {
|
|
11818
|
+
if (!this._queue) return;
|
|
11819
|
+
this._queue.registerHandler("report", async (payload) => {
|
|
11820
|
+
const { error } = await this.supabase.from("reports").insert(payload).select("id").single();
|
|
11821
|
+
if (error) return { success: false, error: error.message };
|
|
11822
|
+
return { success: true };
|
|
11823
|
+
});
|
|
11824
|
+
this._queue.registerHandler("message", async (payload) => {
|
|
11825
|
+
const { error } = await this.supabase.from("discussion_messages").insert(payload);
|
|
11826
|
+
if (error) return { success: false, error: error.message };
|
|
11827
|
+
return { success: true };
|
|
11828
|
+
});
|
|
11829
|
+
this._queue.registerHandler("feedback", async (payload) => {
|
|
11830
|
+
const { error } = await this.supabase.from("test_feedback").insert(payload);
|
|
11831
|
+
if (error) return { success: false, error: error.message };
|
|
11832
|
+
return { success: true };
|
|
11833
|
+
});
|
|
11834
|
+
}
|
|
11835
|
+
// ── Realtime Subscriptions ─────────────────────────────────
|
|
11836
|
+
/** Whether realtime is enabled in config. */
|
|
11837
|
+
get realtimeEnabled() {
|
|
11838
|
+
return !!this.config.realtime?.enabled;
|
|
11839
|
+
}
|
|
11840
|
+
/**
|
|
11841
|
+
* Subscribe to postgres_changes on relevant tables.
|
|
11842
|
+
* Each callback fires when the corresponding table has changes —
|
|
11843
|
+
* the provider should call its refresh function in response.
|
|
11844
|
+
* Returns a cleanup function that unsubscribes all channels.
|
|
11845
|
+
*/
|
|
11846
|
+
subscribeToChanges(callbacks) {
|
|
11847
|
+
this.unsubscribeAll();
|
|
11848
|
+
const projectId = this.config.projectId;
|
|
11849
|
+
const debounce = (fn, ms = 500) => {
|
|
11850
|
+
let timer;
|
|
11851
|
+
return () => {
|
|
11852
|
+
clearTimeout(timer);
|
|
11853
|
+
timer = setTimeout(fn, ms);
|
|
11854
|
+
};
|
|
11855
|
+
};
|
|
11856
|
+
if (callbacks.onAssignmentChange) {
|
|
11857
|
+
const debouncedCb = debounce(callbacks.onAssignmentChange);
|
|
11858
|
+
const channel = this.supabase.channel("bugbear-assignments").on("postgres_changes", {
|
|
11859
|
+
event: "*",
|
|
11860
|
+
schema: "public",
|
|
11861
|
+
table: "test_assignments",
|
|
11862
|
+
filter: `project_id=eq.${projectId}`
|
|
11863
|
+
}, debouncedCb).subscribe((status) => {
|
|
11864
|
+
if (status === "CHANNEL_ERROR") {
|
|
11865
|
+
console.warn("BugBear: Realtime subscription failed for test_assignments");
|
|
11866
|
+
}
|
|
11867
|
+
});
|
|
11868
|
+
this.realtimeChannels.push(channel);
|
|
11869
|
+
}
|
|
11870
|
+
if (callbacks.onMessageChange) {
|
|
11871
|
+
const debouncedCb = debounce(callbacks.onMessageChange);
|
|
11872
|
+
const channel = this.supabase.channel("bugbear-messages").on("postgres_changes", {
|
|
11873
|
+
event: "INSERT",
|
|
11874
|
+
schema: "public",
|
|
11875
|
+
table: "discussion_messages"
|
|
11876
|
+
}, debouncedCb).subscribe((status) => {
|
|
11877
|
+
if (status === "CHANNEL_ERROR") {
|
|
11878
|
+
console.warn("BugBear: Realtime subscription failed for discussion_messages");
|
|
11879
|
+
}
|
|
11880
|
+
});
|
|
11881
|
+
this.realtimeChannels.push(channel);
|
|
11882
|
+
}
|
|
11883
|
+
if (callbacks.onReportChange) {
|
|
11884
|
+
const debouncedCb = debounce(callbacks.onReportChange);
|
|
11885
|
+
const channel = this.supabase.channel("bugbear-reports").on("postgres_changes", {
|
|
11886
|
+
event: "UPDATE",
|
|
11887
|
+
schema: "public",
|
|
11888
|
+
table: "reports",
|
|
11889
|
+
filter: `project_id=eq.${projectId}`
|
|
11890
|
+
}, debouncedCb).subscribe((status) => {
|
|
11891
|
+
if (status === "CHANNEL_ERROR") {
|
|
11892
|
+
console.warn("BugBear: Realtime subscription failed for reports");
|
|
11893
|
+
}
|
|
11894
|
+
});
|
|
11895
|
+
this.realtimeChannels.push(channel);
|
|
11896
|
+
}
|
|
11897
|
+
return () => this.unsubscribeAll();
|
|
11898
|
+
}
|
|
11899
|
+
/** Remove all active Realtime channels. */
|
|
11900
|
+
unsubscribeAll() {
|
|
11901
|
+
for (const channel of this.realtimeChannels) {
|
|
11902
|
+
this.supabase.removeChannel(channel);
|
|
11903
|
+
}
|
|
11904
|
+
this.realtimeChannels = [];
|
|
11630
11905
|
}
|
|
11631
11906
|
/**
|
|
11632
11907
|
* Track navigation for context.
|
|
@@ -11686,6 +11961,7 @@ var BugBearClient = class {
|
|
|
11686
11961
|
return { success: false, error: "A report is already being submitted" };
|
|
11687
11962
|
}
|
|
11688
11963
|
this.reportSubmitInFlight = true;
|
|
11964
|
+
let fullReport;
|
|
11689
11965
|
try {
|
|
11690
11966
|
const validationError = this.validateReport(report);
|
|
11691
11967
|
if (validationError) {
|
|
@@ -11702,7 +11978,7 @@ var BugBearClient = class {
|
|
|
11702
11978
|
return { success: false, error: "User not authenticated" };
|
|
11703
11979
|
}
|
|
11704
11980
|
const testerInfo = await this.getTesterInfo();
|
|
11705
|
-
|
|
11981
|
+
fullReport = {
|
|
11706
11982
|
project_id: this.config.projectId,
|
|
11707
11983
|
reporter_id: userInfo.id,
|
|
11708
11984
|
// User ID from host app (required)
|
|
@@ -11729,6 +12005,10 @@ var BugBearClient = class {
|
|
|
11729
12005
|
};
|
|
11730
12006
|
const { data, error } = await this.supabase.from("reports").insert(fullReport).select("id").single();
|
|
11731
12007
|
if (error) {
|
|
12008
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
12009
|
+
await this._queue.enqueue("report", fullReport);
|
|
12010
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
12011
|
+
}
|
|
11732
12012
|
console.error("BugBear: Failed to submit report", error.message);
|
|
11733
12013
|
return { success: false, error: error.message };
|
|
11734
12014
|
}
|
|
@@ -11738,6 +12018,10 @@ var BugBearClient = class {
|
|
|
11738
12018
|
return { success: true, reportId: data.id };
|
|
11739
12019
|
} catch (err) {
|
|
11740
12020
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
12021
|
+
if (this._queue && fullReport && isNetworkError(message)) {
|
|
12022
|
+
await this._queue.enqueue("report", fullReport);
|
|
12023
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
12024
|
+
}
|
|
11741
12025
|
return { success: false, error: message };
|
|
11742
12026
|
} finally {
|
|
11743
12027
|
this.reportSubmitInFlight = false;
|
|
@@ -11747,10 +12031,13 @@ var BugBearClient = class {
|
|
|
11747
12031
|
* Get assigned tests for current user
|
|
11748
12032
|
* First looks up the tester by email, then fetches their assignments
|
|
11749
12033
|
*/
|
|
11750
|
-
async getAssignedTests() {
|
|
12034
|
+
async getAssignedTests(options) {
|
|
11751
12035
|
try {
|
|
11752
12036
|
const testerInfo = await this.getTesterInfo();
|
|
11753
12037
|
if (!testerInfo) return [];
|
|
12038
|
+
const pageSize = Math.min(options?.pageSize ?? 100, 100);
|
|
12039
|
+
const from = (options?.page ?? 0) * pageSize;
|
|
12040
|
+
const to = from + pageSize - 1;
|
|
11754
12041
|
const { data, error } = await this.supabase.from("test_assignments").select(`
|
|
11755
12042
|
id,
|
|
11756
12043
|
status,
|
|
@@ -11791,12 +12078,18 @@ var BugBearClient = class {
|
|
|
11791
12078
|
login_hint
|
|
11792
12079
|
)
|
|
11793
12080
|
)
|
|
11794
|
-
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).
|
|
12081
|
+
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).range(from, to);
|
|
11795
12082
|
if (error) {
|
|
11796
12083
|
console.error("BugBear: Failed to fetch assignments", formatPgError(error));
|
|
11797
12084
|
return [];
|
|
11798
12085
|
}
|
|
11799
|
-
const mapped = (data || []).
|
|
12086
|
+
const mapped = (data || []).filter((item) => {
|
|
12087
|
+
if (!item.test_case) {
|
|
12088
|
+
console.warn("BugBear: Assignment returned without test_case", { id: item.id });
|
|
12089
|
+
return false;
|
|
12090
|
+
}
|
|
12091
|
+
return true;
|
|
12092
|
+
}).map((item) => ({
|
|
11800
12093
|
id: item.id,
|
|
11801
12094
|
status: item.status,
|
|
11802
12095
|
startedAt: item.started_at,
|
|
@@ -12044,6 +12337,7 @@ var BugBearClient = class {
|
|
|
12044
12337
|
* This empowers testers to shape better tests over time
|
|
12045
12338
|
*/
|
|
12046
12339
|
async submitTestFeedback(options) {
|
|
12340
|
+
let feedbackPayload;
|
|
12047
12341
|
try {
|
|
12048
12342
|
const testerInfo = await this.getTesterInfo();
|
|
12049
12343
|
if (!testerInfo) {
|
|
@@ -12053,7 +12347,17 @@ var BugBearClient = class {
|
|
|
12053
12347
|
if (feedback.rating < 1 || feedback.rating > 5) {
|
|
12054
12348
|
return { success: false, error: "Rating must be between 1 and 5" };
|
|
12055
12349
|
}
|
|
12056
|
-
const
|
|
12350
|
+
const optionalRatings = [
|
|
12351
|
+
{ name: "clarityRating", value: feedback.clarityRating },
|
|
12352
|
+
{ name: "stepsRating", value: feedback.stepsRating },
|
|
12353
|
+
{ name: "relevanceRating", value: feedback.relevanceRating }
|
|
12354
|
+
];
|
|
12355
|
+
for (const { name, value } of optionalRatings) {
|
|
12356
|
+
if (value !== void 0 && value !== null && (value < 1 || value > 5)) {
|
|
12357
|
+
return { success: false, error: `${name} must be between 1 and 5` };
|
|
12358
|
+
}
|
|
12359
|
+
}
|
|
12360
|
+
feedbackPayload = {
|
|
12057
12361
|
project_id: this.config.projectId,
|
|
12058
12362
|
test_case_id: testCaseId,
|
|
12059
12363
|
assignment_id: assignmentId || null,
|
|
@@ -12071,8 +12375,13 @@ var BugBearClient = class {
|
|
|
12071
12375
|
platform: this.getDeviceInfo().platform,
|
|
12072
12376
|
time_to_complete_seconds: timeToCompleteSeconds || null,
|
|
12073
12377
|
screenshot_urls: screenshotUrls || []
|
|
12074
|
-
}
|
|
12378
|
+
};
|
|
12379
|
+
const { error: feedbackError } = await this.supabase.from("test_feedback").insert(feedbackPayload);
|
|
12075
12380
|
if (feedbackError) {
|
|
12381
|
+
if (this._queue && isNetworkError(feedbackError.message)) {
|
|
12382
|
+
await this._queue.enqueue("feedback", feedbackPayload);
|
|
12383
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
12384
|
+
}
|
|
12076
12385
|
console.error("BugBear: Failed to submit feedback", feedbackError);
|
|
12077
12386
|
return { success: false, error: feedbackError.message };
|
|
12078
12387
|
}
|
|
@@ -12089,6 +12398,10 @@ var BugBearClient = class {
|
|
|
12089
12398
|
return { success: true };
|
|
12090
12399
|
} catch (err) {
|
|
12091
12400
|
const message = err instanceof Error ? err.message : "Unknown error";
|
|
12401
|
+
if (this._queue && feedbackPayload && isNetworkError(message)) {
|
|
12402
|
+
await this._queue.enqueue("feedback", feedbackPayload);
|
|
12403
|
+
return { success: false, queued: true, error: "Queued \u2014 will send when online" };
|
|
12404
|
+
}
|
|
12092
12405
|
console.error("BugBear: Error submitting feedback", err);
|
|
12093
12406
|
return { success: false, error: message };
|
|
12094
12407
|
}
|
|
@@ -12723,6 +13036,7 @@ var BugBearClient = class {
|
|
|
12723
13036
|
* Send a message to a thread
|
|
12724
13037
|
*/
|
|
12725
13038
|
async sendMessage(threadId, content, attachments) {
|
|
13039
|
+
let insertData;
|
|
12726
13040
|
try {
|
|
12727
13041
|
const testerInfo = await this.getTesterInfo();
|
|
12728
13042
|
if (!testerInfo) {
|
|
@@ -12734,7 +13048,7 @@ var BugBearClient = class {
|
|
|
12734
13048
|
console.error("BugBear: Rate limit exceeded for messages");
|
|
12735
13049
|
return false;
|
|
12736
13050
|
}
|
|
12737
|
-
|
|
13051
|
+
insertData = {
|
|
12738
13052
|
thread_id: threadId,
|
|
12739
13053
|
sender_type: "tester",
|
|
12740
13054
|
sender_tester_id: testerInfo.id,
|
|
@@ -12749,12 +13063,21 @@ var BugBearClient = class {
|
|
|
12749
13063
|
}
|
|
12750
13064
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
12751
13065
|
if (error) {
|
|
13066
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
13067
|
+
await this._queue.enqueue("message", insertData);
|
|
13068
|
+
return false;
|
|
13069
|
+
}
|
|
12752
13070
|
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
12753
13071
|
return false;
|
|
12754
13072
|
}
|
|
12755
13073
|
await this.markThreadAsRead(threadId);
|
|
12756
13074
|
return true;
|
|
12757
13075
|
} catch (err) {
|
|
13076
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
13077
|
+
if (this._queue && insertData && isNetworkError(message)) {
|
|
13078
|
+
await this._queue.enqueue("message", insertData);
|
|
13079
|
+
return false;
|
|
13080
|
+
}
|
|
12758
13081
|
console.error("BugBear: Error sending message", err);
|
|
12759
13082
|
return false;
|
|
12760
13083
|
}
|
|
@@ -12941,7 +13264,7 @@ var BugBearClient = class {
|
|
|
12941
13264
|
console.error("BugBear: Failed to fetch session history", formatPgError(error));
|
|
12942
13265
|
return [];
|
|
12943
13266
|
}
|
|
12944
|
-
return (data || []).map((
|
|
13267
|
+
return (data || []).map((s2) => this.transformSession(s2));
|
|
12945
13268
|
} catch (err) {
|
|
12946
13269
|
console.error("BugBear: Error fetching session history", err);
|
|
12947
13270
|
return [];
|
|
@@ -13089,7 +13412,7 @@ function createBugBear(config) {
|
|
|
13089
13412
|
}
|
|
13090
13413
|
|
|
13091
13414
|
// src/BugBearProvider.tsx
|
|
13092
|
-
import { Platform, Dimensions } from "react-native";
|
|
13415
|
+
import { Platform, Dimensions, AppState } from "react-native";
|
|
13093
13416
|
var BugBearContext = createContext({
|
|
13094
13417
|
client: null,
|
|
13095
13418
|
isTester: false,
|
|
@@ -13130,6 +13453,7 @@ var BugBearContext = createContext({
|
|
|
13130
13453
|
issueCounts: { open: 0, done: 0, reopened: 0 },
|
|
13131
13454
|
refreshIssueCounts: async () => {
|
|
13132
13455
|
},
|
|
13456
|
+
queuedCount: 0,
|
|
13133
13457
|
dashboardUrl: void 0,
|
|
13134
13458
|
onError: void 0
|
|
13135
13459
|
});
|
|
@@ -13146,6 +13470,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13146
13470
|
const [threads, setThreads] = useState([]);
|
|
13147
13471
|
const [unreadCount, setUnreadCount] = useState(0);
|
|
13148
13472
|
const [issueCounts, setIssueCounts] = useState({ open: 0, done: 0, reopened: 0 });
|
|
13473
|
+
const [queuedCount, setQueuedCount] = useState(0);
|
|
13149
13474
|
const [activeSession, setActiveSession] = useState(null);
|
|
13150
13475
|
const [sessionFindings, setSessionFindings] = useState([]);
|
|
13151
13476
|
const hasInitialized = useRef(false);
|
|
@@ -13305,18 +13630,46 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13305
13630
|
hasInitialized.current = true;
|
|
13306
13631
|
contextCapture.startCapture();
|
|
13307
13632
|
const newClient = createBugBear(config);
|
|
13633
|
+
if (newClient.queue) {
|
|
13634
|
+
newClient.queue.onChange(setQueuedCount);
|
|
13635
|
+
newClient.initQueue();
|
|
13636
|
+
}
|
|
13308
13637
|
setClient(newClient);
|
|
13309
13638
|
initializeBugBear(newClient);
|
|
13310
13639
|
}
|
|
13311
13640
|
}, [enabled, config, initializeBugBear]);
|
|
13641
|
+
useEffect(() => {
|
|
13642
|
+
if (!client?.queue) return;
|
|
13643
|
+
const subscription = AppState.addEventListener("change", (state) => {
|
|
13644
|
+
if (state === "active" && client.queue && client.queue.count > 0) {
|
|
13645
|
+
client.queue.flush();
|
|
13646
|
+
}
|
|
13647
|
+
});
|
|
13648
|
+
if (client.queue.count > 0) {
|
|
13649
|
+
client.queue.flush();
|
|
13650
|
+
}
|
|
13651
|
+
return () => subscription.remove();
|
|
13652
|
+
}, [client]);
|
|
13312
13653
|
useEffect(() => {
|
|
13313
13654
|
if (!client || !isTester || !isQAEnabled) return;
|
|
13655
|
+
let unsubscribe;
|
|
13656
|
+
if (client.realtimeEnabled) {
|
|
13657
|
+
unsubscribe = client.subscribeToChanges({
|
|
13658
|
+
onAssignmentChange: refreshAssignments,
|
|
13659
|
+
onMessageChange: refreshThreads,
|
|
13660
|
+
onReportChange: refreshIssueCounts
|
|
13661
|
+
});
|
|
13662
|
+
}
|
|
13663
|
+
const pollInterval = client.realtimeEnabled ? 12e4 : 3e4;
|
|
13314
13664
|
const interval = setInterval(() => {
|
|
13315
13665
|
refreshThreads();
|
|
13316
13666
|
refreshIssueCounts();
|
|
13317
|
-
},
|
|
13318
|
-
return () =>
|
|
13319
|
-
|
|
13667
|
+
}, pollInterval);
|
|
13668
|
+
return () => {
|
|
13669
|
+
clearInterval(interval);
|
|
13670
|
+
unsubscribe?.();
|
|
13671
|
+
};
|
|
13672
|
+
}, [client, isTester, isQAEnabled, refreshThreads, refreshIssueCounts, refreshAssignments]);
|
|
13320
13673
|
const currentAssignment = assignments.find(
|
|
13321
13674
|
(a) => a.status === "in_progress"
|
|
13322
13675
|
) || assignments.find(
|
|
@@ -13359,6 +13712,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13359
13712
|
// Issue tracking
|
|
13360
13713
|
issueCounts,
|
|
13361
13714
|
refreshIssueCounts,
|
|
13715
|
+
queuedCount,
|
|
13362
13716
|
dashboardUrl: config.dashboardUrl,
|
|
13363
13717
|
onError: config.onError
|
|
13364
13718
|
}
|
|
@@ -13368,21 +13722,21 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13368
13722
|
}
|
|
13369
13723
|
|
|
13370
13724
|
// src/BugBearButton.tsx
|
|
13371
|
-
import
|
|
13725
|
+
import React18, { useState as useState12, useRef as useRef4 } from "react";
|
|
13372
13726
|
import {
|
|
13373
|
-
View as
|
|
13727
|
+
View as View17,
|
|
13374
13728
|
Text as Text16,
|
|
13375
13729
|
Image as Image4,
|
|
13376
13730
|
TouchableOpacity as TouchableOpacity15,
|
|
13377
13731
|
Modal as Modal3,
|
|
13378
13732
|
ScrollView as ScrollView3,
|
|
13379
|
-
StyleSheet as
|
|
13733
|
+
StyleSheet as StyleSheet18,
|
|
13380
13734
|
Dimensions as Dimensions2,
|
|
13381
13735
|
KeyboardAvoidingView,
|
|
13382
13736
|
Platform as Platform4,
|
|
13383
13737
|
PanResponder,
|
|
13384
|
-
Animated,
|
|
13385
|
-
ActivityIndicator as
|
|
13738
|
+
Animated as Animated2,
|
|
13739
|
+
ActivityIndicator as ActivityIndicator2,
|
|
13386
13740
|
Keyboard as Keyboard2
|
|
13387
13741
|
} from "react-native";
|
|
13388
13742
|
|
|
@@ -13528,9 +13882,9 @@ var shared = StyleSheet.create({
|
|
|
13528
13882
|
function formatElapsedTime(seconds) {
|
|
13529
13883
|
const h = Math.floor(seconds / 3600);
|
|
13530
13884
|
const m = Math.floor(seconds % 3600 / 60);
|
|
13531
|
-
const
|
|
13532
|
-
if (h > 0) return `${h}:${m.toString().padStart(2, "0")}:${
|
|
13533
|
-
return `${m}:${
|
|
13885
|
+
const s2 = seconds % 60;
|
|
13886
|
+
if (h > 0) return `${h}:${m.toString().padStart(2, "0")}:${s2.toString().padStart(2, "0")}`;
|
|
13887
|
+
return `${m}:${s2.toString().padStart(2, "0")}`;
|
|
13534
13888
|
}
|
|
13535
13889
|
function formatRelativeTime(dateString) {
|
|
13536
13890
|
const date = new Date(dateString);
|
|
@@ -13602,11 +13956,90 @@ var templateInfo = {
|
|
|
13602
13956
|
};
|
|
13603
13957
|
|
|
13604
13958
|
// src/widget/screens/HomeScreen.tsx
|
|
13605
|
-
import
|
|
13606
|
-
import { View, Text, TouchableOpacity, StyleSheet as
|
|
13607
|
-
|
|
13608
|
-
|
|
13959
|
+
import React3, { useEffect as useEffect3 } from "react";
|
|
13960
|
+
import { View as View2, Text, TouchableOpacity, StyleSheet as StyleSheet3, Linking } from "react-native";
|
|
13961
|
+
|
|
13962
|
+
// src/widget/Skeleton.tsx
|
|
13963
|
+
import React2, { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
13964
|
+
import { View, Animated, StyleSheet as StyleSheet2 } from "react-native";
|
|
13965
|
+
function usePulse(delay = 0) {
|
|
13966
|
+
const opacity = useRef2(new Animated.Value(0.6)).current;
|
|
13609
13967
|
useEffect2(() => {
|
|
13968
|
+
const timeout = setTimeout(() => {
|
|
13969
|
+
Animated.loop(
|
|
13970
|
+
Animated.sequence([
|
|
13971
|
+
Animated.timing(opacity, { toValue: 0.25, duration: 750, useNativeDriver: true }),
|
|
13972
|
+
Animated.timing(opacity, { toValue: 0.6, duration: 750, useNativeDriver: true })
|
|
13973
|
+
])
|
|
13974
|
+
).start();
|
|
13975
|
+
}, delay);
|
|
13976
|
+
return () => clearTimeout(timeout);
|
|
13977
|
+
}, [opacity, delay]);
|
|
13978
|
+
return opacity;
|
|
13979
|
+
}
|
|
13980
|
+
function Bar({ width = "100%", height = 12, radius = 6, delay = 0 }) {
|
|
13981
|
+
const opacity = usePulse(delay);
|
|
13982
|
+
return /* @__PURE__ */ React2.createElement(
|
|
13983
|
+
Animated.View,
|
|
13984
|
+
{
|
|
13985
|
+
style: {
|
|
13986
|
+
width,
|
|
13987
|
+
height,
|
|
13988
|
+
borderRadius: radius,
|
|
13989
|
+
backgroundColor: colors.border,
|
|
13990
|
+
opacity
|
|
13991
|
+
}
|
|
13992
|
+
}
|
|
13993
|
+
);
|
|
13994
|
+
}
|
|
13995
|
+
function Circle({ size = 20, delay = 0 }) {
|
|
13996
|
+
const opacity = usePulse(delay);
|
|
13997
|
+
return /* @__PURE__ */ React2.createElement(
|
|
13998
|
+
Animated.View,
|
|
13999
|
+
{
|
|
14000
|
+
style: {
|
|
14001
|
+
width: size,
|
|
14002
|
+
height: size,
|
|
14003
|
+
borderRadius: size / 2,
|
|
14004
|
+
backgroundColor: colors.border,
|
|
14005
|
+
opacity
|
|
14006
|
+
}
|
|
14007
|
+
}
|
|
14008
|
+
);
|
|
14009
|
+
}
|
|
14010
|
+
function HomeScreenSkeleton() {
|
|
14011
|
+
return /* @__PURE__ */ React2.createElement(View, null, /* @__PURE__ */ React2.createElement(Bar, { width: "100%", height: 100, radius: 16 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 20 } }), /* @__PURE__ */ React2.createElement(View, { style: s.actionGrid }, [0, 1, 2, 3].map((i) => /* @__PURE__ */ React2.createElement(View, { key: i, style: s.actionCard }, /* @__PURE__ */ React2.createElement(Circle, { size: 28, delay: i * 80 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 8 } }), /* @__PURE__ */ React2.createElement(Bar, { width: 60, height: 10, delay: i * 80 })))), /* @__PURE__ */ React2.createElement(View, { style: s.issueGrid }, [0, 1, 2].map((i) => /* @__PURE__ */ React2.createElement(View, { key: i, style: s.issueCard }, /* @__PURE__ */ React2.createElement(Bar, { width: 30, height: 18, delay: i * 100 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 6 } }), /* @__PURE__ */ React2.createElement(Bar, { width: 40, height: 8, delay: i * 100 })))), /* @__PURE__ */ React2.createElement(Bar, { width: "100%", height: 6, radius: 3 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 8 } }), /* @__PURE__ */ React2.createElement(View, { style: { alignItems: "center" } }, /* @__PURE__ */ React2.createElement(Bar, { width: 120, height: 10 })));
|
|
14012
|
+
}
|
|
14013
|
+
function TestItemSkeleton({ delay = 0 }) {
|
|
14014
|
+
return /* @__PURE__ */ React2.createElement(View, { style: s.testItem }, /* @__PURE__ */ React2.createElement(Circle, { size: 18, delay }), /* @__PURE__ */ React2.createElement(View, { style: { flex: 1, marginLeft: 10 } }, /* @__PURE__ */ React2.createElement(Bar, { width: "70%", height: 11, delay }), /* @__PURE__ */ React2.createElement(View, { style: { height: 4 } }), /* @__PURE__ */ React2.createElement(Bar, { width: "45%", height: 8, delay })), /* @__PURE__ */ React2.createElement(Bar, { width: 50, height: 18, radius: 6, delay }));
|
|
14015
|
+
}
|
|
14016
|
+
function TestListScreenSkeleton() {
|
|
14017
|
+
return /* @__PURE__ */ React2.createElement(View, null, /* @__PURE__ */ React2.createElement(View, { style: s.filterRow }, [55, 50, 50, 70].map((w, i) => /* @__PURE__ */ React2.createElement(Bar, { key: i, width: w, height: 28, radius: 8, delay: i * 50 }))), /* @__PURE__ */ React2.createElement(Bar, { width: "100%", height: 36, radius: 8 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 10 } }), [0, 1].map((g) => /* @__PURE__ */ React2.createElement(View, { key: g, style: { marginBottom: 12 } }, /* @__PURE__ */ React2.createElement(View, { style: s.folderHeader }, /* @__PURE__ */ React2.createElement(Bar, { width: 12, height: 10 }), /* @__PURE__ */ React2.createElement(Bar, { width: "40%", height: 12 }), /* @__PURE__ */ React2.createElement(View, { style: { flex: 1 } }), /* @__PURE__ */ React2.createElement(Bar, { width: 40, height: 4, radius: 2 }), /* @__PURE__ */ React2.createElement(Bar, { width: 24, height: 10 })), [0, 1, 2].map((i) => /* @__PURE__ */ React2.createElement(TestItemSkeleton, { key: i, delay: (g * 3 + i) * 80 })))));
|
|
14018
|
+
}
|
|
14019
|
+
function IssueListScreenSkeleton() {
|
|
14020
|
+
return /* @__PURE__ */ React2.createElement(View, null, [0, 1, 2, 3].map((i) => /* @__PURE__ */ React2.createElement(View, { key: i, style: s.issueRow }, /* @__PURE__ */ React2.createElement(View, { style: s.issueRowTop }, /* @__PURE__ */ React2.createElement(Circle, { size: 8, delay: i * 100 }), /* @__PURE__ */ React2.createElement(Bar, { width: "65%", height: 11, delay: i * 100 })), /* @__PURE__ */ React2.createElement(View, { style: s.issueRowBottom }, /* @__PURE__ */ React2.createElement(Bar, { width: "40%", height: 8, delay: i * 100 }), /* @__PURE__ */ React2.createElement(Bar, { width: 40, height: 8, delay: i * 100 })))));
|
|
14021
|
+
}
|
|
14022
|
+
function MessageListScreenSkeleton() {
|
|
14023
|
+
return /* @__PURE__ */ React2.createElement(View, null, /* @__PURE__ */ React2.createElement(Bar, { width: "100%", height: 44, radius: 12 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 16 } }), [0, 1, 2, 3].map((i) => /* @__PURE__ */ React2.createElement(View, { key: i, style: s.threadRow }, /* @__PURE__ */ React2.createElement(Circle, { size: 20, delay: i * 100 }), /* @__PURE__ */ React2.createElement(View, { style: { flex: 1, marginLeft: 10 } }, /* @__PURE__ */ React2.createElement(Bar, { width: "55%", height: 11, delay: i * 100 }), /* @__PURE__ */ React2.createElement(View, { style: { height: 5 } }), /* @__PURE__ */ React2.createElement(Bar, { width: "80%", height: 9, delay: i * 100 })), /* @__PURE__ */ React2.createElement(Bar, { width: 30, height: 8, delay: i * 100 }))));
|
|
14024
|
+
}
|
|
14025
|
+
var s = StyleSheet2.create({
|
|
14026
|
+
actionGrid: { flexDirection: "row", flexWrap: "wrap", gap: 12, marginBottom: 20 },
|
|
14027
|
+
actionCard: { width: "47%", backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 12, padding: 16, alignItems: "center" },
|
|
14028
|
+
issueGrid: { flexDirection: "row", gap: 10, marginBottom: 20 },
|
|
14029
|
+
issueCard: { flex: 1, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 10, paddingVertical: 12, paddingHorizontal: 8, alignItems: "center" },
|
|
14030
|
+
filterRow: { flexDirection: "row", gap: 8, marginBottom: 8 },
|
|
14031
|
+
folderHeader: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 8, paddingHorizontal: 4 },
|
|
14032
|
+
testItem: { flexDirection: "row", alignItems: "center", paddingVertical: 10, paddingHorizontal: 12, borderRadius: 8, marginBottom: 4, backgroundColor: colors.card },
|
|
14033
|
+
issueRow: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 10, padding: 14, marginBottom: 8 },
|
|
14034
|
+
issueRowTop: { flexDirection: "row", alignItems: "center", gap: 8 },
|
|
14035
|
+
issueRowBottom: { flexDirection: "row", justifyContent: "space-between", marginTop: 8 },
|
|
14036
|
+
threadRow: { flexDirection: "row", alignItems: "flex-start", padding: 12, borderRadius: 10, marginBottom: 4, backgroundColor: colors.card }
|
|
14037
|
+
});
|
|
14038
|
+
|
|
14039
|
+
// src/widget/screens/HomeScreen.tsx
|
|
14040
|
+
function HomeScreen({ nav }) {
|
|
14041
|
+
const { assignments, unreadCount, threads, refreshAssignments, refreshThreads, issueCounts, refreshIssueCounts, dashboardUrl, isLoading } = useBugBear();
|
|
14042
|
+
useEffect3(() => {
|
|
13610
14043
|
refreshAssignments();
|
|
13611
14044
|
refreshThreads();
|
|
13612
14045
|
refreshIssueCounts();
|
|
@@ -13616,103 +14049,104 @@ function HomeScreen({ nav }) {
|
|
|
13616
14049
|
const retestCount = pendingAssignments.filter((a) => a.isVerification).length;
|
|
13617
14050
|
const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
|
|
13618
14051
|
const totalTests = assignments.length;
|
|
13619
|
-
return /* @__PURE__ */
|
|
14052
|
+
if (isLoading) return /* @__PURE__ */ React3.createElement(HomeScreenSkeleton, null);
|
|
14053
|
+
return /* @__PURE__ */ React3.createElement(View2, null, pendingCount > 0 ? /* @__PURE__ */ React3.createElement(
|
|
13620
14054
|
TouchableOpacity,
|
|
13621
14055
|
{
|
|
13622
14056
|
style: [styles.heroBanner, styles.heroBannerTests],
|
|
13623
14057
|
onPress: () => nav.push({ name: "TEST_DETAIL" }),
|
|
13624
14058
|
activeOpacity: 0.8
|
|
13625
14059
|
},
|
|
13626
|
-
/* @__PURE__ */
|
|
13627
|
-
/* @__PURE__ */
|
|
13628
|
-
retestCount > 0 && /* @__PURE__ */
|
|
13629
|
-
/* @__PURE__ */
|
|
13630
|
-
) : unreadCount > 0 ? /* @__PURE__ */
|
|
14060
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.heroCount }, pendingCount),
|
|
14061
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.heroLabel }, "test", pendingCount !== 1 ? "s" : "", " waiting"),
|
|
14062
|
+
retestCount > 0 && /* @__PURE__ */ React3.createElement(View2, { style: styles.retestPill }, /* @__PURE__ */ React3.createElement(Text, { style: styles.retestPillText }, "\u{1F504} ", retestCount, " retest", retestCount !== 1 ? "s" : "")),
|
|
14063
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.heroAction }, "Start Testing \u2192")
|
|
14064
|
+
) : unreadCount > 0 ? /* @__PURE__ */ React3.createElement(
|
|
13631
14065
|
TouchableOpacity,
|
|
13632
14066
|
{
|
|
13633
14067
|
style: [styles.heroBanner, styles.heroBannerMessages],
|
|
13634
14068
|
onPress: () => nav.push({ name: "MESSAGE_LIST" }),
|
|
13635
14069
|
activeOpacity: 0.8
|
|
13636
14070
|
},
|
|
13637
|
-
/* @__PURE__ */
|
|
13638
|
-
/* @__PURE__ */
|
|
13639
|
-
/* @__PURE__ */
|
|
13640
|
-
) : /* @__PURE__ */
|
|
14071
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.heroCount }, unreadCount),
|
|
14072
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.heroLabel }, "unread message", unreadCount !== 1 ? "s" : ""),
|
|
14073
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.heroAction }, "View Messages \u2192")
|
|
14074
|
+
) : /* @__PURE__ */ React3.createElement(View2, { style: [styles.heroBanner, styles.heroBannerClear] }, /* @__PURE__ */ React3.createElement(Text, { style: styles.heroClearEmoji }, "\u2705"), /* @__PURE__ */ React3.createElement(Text, { style: styles.heroClearTitle }, "All caught up!"), totalTests > 0 && /* @__PURE__ */ React3.createElement(Text, { style: styles.heroClearSub }, completedCount, "/", totalTests, " tests completed")), /* @__PURE__ */ React3.createElement(View2, { style: styles.actionGrid }, /* @__PURE__ */ React3.createElement(
|
|
13641
14075
|
TouchableOpacity,
|
|
13642
14076
|
{
|
|
13643
14077
|
style: styles.actionCard,
|
|
13644
14078
|
onPress: () => nav.push({ name: "TEST_LIST" }),
|
|
13645
14079
|
activeOpacity: 0.7
|
|
13646
14080
|
},
|
|
13647
|
-
/* @__PURE__ */
|
|
13648
|
-
/* @__PURE__ */
|
|
13649
|
-
pendingCount > 0 && /* @__PURE__ */
|
|
13650
|
-
), /* @__PURE__ */
|
|
14081
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.actionIcon }, "\u2705"),
|
|
14082
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.actionLabel }, "Tests"),
|
|
14083
|
+
pendingCount > 0 && /* @__PURE__ */ React3.createElement(View2, { style: styles.actionBadge }, /* @__PURE__ */ React3.createElement(Text, { style: styles.actionBadgeText }, pendingCount))
|
|
14084
|
+
), /* @__PURE__ */ React3.createElement(
|
|
13651
14085
|
TouchableOpacity,
|
|
13652
14086
|
{
|
|
13653
14087
|
style: styles.actionCard,
|
|
13654
14088
|
onPress: () => nav.push({ name: "REPORT", prefill: { type: "bug" } }),
|
|
13655
14089
|
activeOpacity: 0.7
|
|
13656
14090
|
},
|
|
13657
|
-
/* @__PURE__ */
|
|
13658
|
-
/* @__PURE__ */
|
|
13659
|
-
), /* @__PURE__ */
|
|
14091
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.actionIcon }, "\u{1F41B}"),
|
|
14092
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.actionLabel }, "Report Bug")
|
|
14093
|
+
), /* @__PURE__ */ React3.createElement(
|
|
13660
14094
|
TouchableOpacity,
|
|
13661
14095
|
{
|
|
13662
14096
|
style: styles.actionCard,
|
|
13663
14097
|
onPress: () => nav.push({ name: "REPORT", prefill: { type: "feedback" } }),
|
|
13664
14098
|
activeOpacity: 0.7
|
|
13665
14099
|
},
|
|
13666
|
-
/* @__PURE__ */
|
|
13667
|
-
/* @__PURE__ */
|
|
13668
|
-
), /* @__PURE__ */
|
|
14100
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.actionIcon }, "\u{1F4A1}"),
|
|
14101
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.actionLabel }, "Feedback")
|
|
14102
|
+
), /* @__PURE__ */ React3.createElement(
|
|
13669
14103
|
TouchableOpacity,
|
|
13670
14104
|
{
|
|
13671
14105
|
style: styles.actionCard,
|
|
13672
14106
|
onPress: () => nav.push({ name: "MESSAGE_LIST" }),
|
|
13673
14107
|
activeOpacity: 0.7
|
|
13674
14108
|
},
|
|
13675
|
-
/* @__PURE__ */
|
|
13676
|
-
/* @__PURE__ */
|
|
13677
|
-
unreadCount > 0 && /* @__PURE__ */
|
|
13678
|
-
)), /* @__PURE__ */
|
|
14109
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.actionIcon }, "\u{1F4AC}"),
|
|
14110
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.actionLabel }, "Messages"),
|
|
14111
|
+
unreadCount > 0 && /* @__PURE__ */ React3.createElement(View2, { style: [styles.actionBadge, styles.actionBadgeMsg] }, /* @__PURE__ */ React3.createElement(Text, { style: styles.actionBadgeText }, unreadCount))
|
|
14112
|
+
)), /* @__PURE__ */ React3.createElement(View2, { style: styles.issueGrid }, /* @__PURE__ */ React3.createElement(
|
|
13679
14113
|
TouchableOpacity,
|
|
13680
14114
|
{
|
|
13681
14115
|
style: [styles.issueCard, styles.issueCardOpen],
|
|
13682
14116
|
onPress: () => nav.push({ name: "ISSUE_LIST", category: "open" }),
|
|
13683
14117
|
activeOpacity: 0.7
|
|
13684
14118
|
},
|
|
13685
|
-
/* @__PURE__ */
|
|
13686
|
-
/* @__PURE__ */
|
|
13687
|
-
), /* @__PURE__ */
|
|
14119
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.issueCountOpen }, issueCounts.open),
|
|
14120
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.issueLabel }, "Open")
|
|
14121
|
+
), /* @__PURE__ */ React3.createElement(
|
|
13688
14122
|
TouchableOpacity,
|
|
13689
14123
|
{
|
|
13690
14124
|
style: [styles.issueCard, styles.issueCardDone],
|
|
13691
14125
|
onPress: () => nav.push({ name: "ISSUE_LIST", category: "done" }),
|
|
13692
14126
|
activeOpacity: 0.7
|
|
13693
14127
|
},
|
|
13694
|
-
/* @__PURE__ */
|
|
13695
|
-
/* @__PURE__ */
|
|
13696
|
-
), /* @__PURE__ */
|
|
14128
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.issueCountDone }, issueCounts.done),
|
|
14129
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.issueLabel }, "Done")
|
|
14130
|
+
), /* @__PURE__ */ React3.createElement(
|
|
13697
14131
|
TouchableOpacity,
|
|
13698
14132
|
{
|
|
13699
14133
|
style: [styles.issueCard, styles.issueCardReopened],
|
|
13700
14134
|
onPress: () => nav.push({ name: "ISSUE_LIST", category: "reopened" }),
|
|
13701
14135
|
activeOpacity: 0.7
|
|
13702
14136
|
},
|
|
13703
|
-
/* @__PURE__ */
|
|
13704
|
-
/* @__PURE__ */
|
|
13705
|
-
)), totalTests > 0 && /* @__PURE__ */
|
|
14137
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.issueCountReopened }, issueCounts.reopened),
|
|
14138
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.issueLabel }, "Reopened")
|
|
14139
|
+
)), totalTests > 0 && /* @__PURE__ */ React3.createElement(View2, { style: styles.progressSection }, /* @__PURE__ */ React3.createElement(View2, { style: styles.progressBar }, /* @__PURE__ */ React3.createElement(View2, { style: [styles.progressFill, { width: `${Math.round(completedCount / totalTests * 100)}%` }] })), /* @__PURE__ */ React3.createElement(Text, { style: styles.progressText }, completedCount, "/", totalTests, " tests completed")), dashboardUrl && /* @__PURE__ */ React3.createElement(
|
|
13706
14140
|
TouchableOpacity,
|
|
13707
14141
|
{
|
|
13708
14142
|
style: styles.webAppLink,
|
|
13709
14143
|
onPress: () => Linking.openURL(dashboardUrl),
|
|
13710
14144
|
activeOpacity: 0.7
|
|
13711
14145
|
},
|
|
13712
|
-
/* @__PURE__ */
|
|
13713
|
-
/* @__PURE__ */
|
|
13714
|
-
/* @__PURE__ */
|
|
13715
|
-
), /* @__PURE__ */
|
|
14146
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.webAppIcon }, "\u{1F310}"),
|
|
14147
|
+
/* @__PURE__ */ React3.createElement(View2, { style: styles.webAppTextWrap }, /* @__PURE__ */ React3.createElement(Text, { style: styles.webAppTitle }, "Open Web Dashboard"), /* @__PURE__ */ React3.createElement(Text, { style: styles.webAppSub }, "View analytics, history & more")),
|
|
14148
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.webAppArrow }, "\u2192")
|
|
14149
|
+
), /* @__PURE__ */ React3.createElement(
|
|
13716
14150
|
TouchableOpacity,
|
|
13717
14151
|
{
|
|
13718
14152
|
style: styles.refreshButton,
|
|
@@ -13722,10 +14156,10 @@ function HomeScreen({ nav }) {
|
|
|
13722
14156
|
refreshIssueCounts();
|
|
13723
14157
|
}
|
|
13724
14158
|
},
|
|
13725
|
-
/* @__PURE__ */
|
|
14159
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.refreshText }, "\u21BB Refresh")
|
|
13726
14160
|
));
|
|
13727
14161
|
}
|
|
13728
|
-
var styles =
|
|
14162
|
+
var styles = StyleSheet3.create({
|
|
13729
14163
|
heroBanner: {
|
|
13730
14164
|
borderRadius: 16,
|
|
13731
14165
|
padding: 24,
|
|
@@ -13949,8 +14383,8 @@ var styles = StyleSheet2.create({
|
|
|
13949
14383
|
});
|
|
13950
14384
|
|
|
13951
14385
|
// src/widget/screens/TestDetailScreen.tsx
|
|
13952
|
-
import
|
|
13953
|
-
import { View as
|
|
14386
|
+
import React4, { useState as useState2, useEffect as useEffect4, useCallback as useCallback2 } from "react";
|
|
14387
|
+
import { View as View3, Text as Text2, TouchableOpacity as TouchableOpacity2, StyleSheet as StyleSheet4, Modal, TextInput, Keyboard } from "react-native";
|
|
13954
14388
|
function TestDetailScreen({ testId, nav }) {
|
|
13955
14389
|
const { client, assignments, currentAssignment, refreshAssignments, getDeviceInfo, onNavigate } = useBugBear();
|
|
13956
14390
|
const displayedAssignment = testId ? assignments.find((a) => a.id === testId) || currentAssignment : currentAssignment;
|
|
@@ -13963,12 +14397,12 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13963
14397
|
const [skipNotes, setSkipNotes] = useState2("");
|
|
13964
14398
|
const [skipping, setSkipping] = useState2(false);
|
|
13965
14399
|
const [isSubmitting, setIsSubmitting] = useState2(false);
|
|
13966
|
-
|
|
14400
|
+
useEffect4(() => {
|
|
13967
14401
|
setCriteriaResults({});
|
|
13968
14402
|
setShowSteps(true);
|
|
13969
14403
|
setShowDetails(false);
|
|
13970
14404
|
}, [displayedAssignment?.id]);
|
|
13971
|
-
|
|
14405
|
+
useEffect4(() => {
|
|
13972
14406
|
const active = displayedAssignment?.status === "in_progress" ? displayedAssignment : null;
|
|
13973
14407
|
if (!active?.startedAt) {
|
|
13974
14408
|
setAssignmentElapsedTime(0);
|
|
@@ -14044,14 +14478,14 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
14044
14478
|
}
|
|
14045
14479
|
}, [client, displayedAssignment, selectedSkipReason, skipNotes, refreshAssignments, assignments, nav]);
|
|
14046
14480
|
if (!displayedAssignment) {
|
|
14047
|
-
return /* @__PURE__ */
|
|
14481
|
+
return /* @__PURE__ */ React4.createElement(View3, { style: shared.emptyState }, /* @__PURE__ */ React4.createElement(Text2, { style: shared.emptyEmoji }, "\u2705"), /* @__PURE__ */ React4.createElement(Text2, { style: shared.emptyTitle }, "No tests assigned"), /* @__PURE__ */ React4.createElement(Text2, { style: shared.emptySubtitle }, "Check back later for new tests"), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [shared.primaryButton, { marginTop: 20, paddingHorizontal: 24 }], onPress: () => nav.reset() }, /* @__PURE__ */ React4.createElement(Text2, { style: shared.primaryButtonText }, "Go Home")));
|
|
14048
14482
|
}
|
|
14049
14483
|
const testCase = displayedAssignment.testCase;
|
|
14050
14484
|
const template = testCase.track?.testTemplate || "steps";
|
|
14051
14485
|
const steps = testCase.steps;
|
|
14052
14486
|
const info = templateInfo[template] || templateInfo.steps;
|
|
14053
14487
|
const rubricMode = testCase.track?.rubricMode || "pass_fail";
|
|
14054
|
-
return /* @__PURE__ */
|
|
14488
|
+
return /* @__PURE__ */ React4.createElement(View3, { style: styles2.container }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.topRow }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.positionInfo }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.positionText }, "Test ", currentIndex + 1, " of ", allTests.length), displayedAssignment.status === "in_progress" && assignmentElapsedTime > 0 && /* @__PURE__ */ React4.createElement(View3, { style: styles2.timerBadge }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.timerText }, formatElapsedTime(assignmentElapsedTime)))), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { onPress: () => nav.push({ name: "TEST_LIST" }) }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.viewAllLink }, "View All \u2192"))), displayedAssignment.isVerification && /* @__PURE__ */ React4.createElement(View3, { style: styles2.retestBanner }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.retestIcon }, "\u{1F504}"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.retestLabel }, "Retest"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.retestSub }, "\u2014 Verify bug fix")), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.testTitle }, testCase.title), testCase.key && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.testKey }, testCase.key), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { onPress: () => setShowSteps(!showSteps), style: styles2.sectionHeader }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.sectionHeaderText }, showSteps ? "\u25BC" : "\u25B6", " ", info.icon, " ", template === "freeform" ? "Instructions" : `${steps.length} ${template === "checklist" ? "items" : template === "rubric" ? "criteria" : "steps"}`)), showSteps && /* @__PURE__ */ React4.createElement(View3, { style: styles2.templateContent }, template === "steps" && steps.map((step, idx) => /* @__PURE__ */ React4.createElement(View3, { key: idx, style: styles2.step }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.stepNumber }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.stepNumberText }, step.stepNumber)), /* @__PURE__ */ React4.createElement(View3, { style: styles2.stepBody }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.stepAction }, step.action), step.expectedResult && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.stepExpected }, "\u2192 ", step.expectedResult)))), template === "checklist" && /* @__PURE__ */ React4.createElement(React4.Fragment, null, steps.map((step, idx) => /* @__PURE__ */ React4.createElement(
|
|
14055
14489
|
TouchableOpacity2,
|
|
14056
14490
|
{
|
|
14057
14491
|
key: idx,
|
|
@@ -14063,31 +14497,31 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
14063
14497
|
}),
|
|
14064
14498
|
style: [styles2.checklistItem, criteriaResults[idx] === true && styles2.checklistItemChecked]
|
|
14065
14499
|
},
|
|
14066
|
-
/* @__PURE__ */
|
|
14067
|
-
/* @__PURE__ */
|
|
14068
|
-
)), Object.keys(criteriaResults).length > 0 && /* @__PURE__ */
|
|
14500
|
+
/* @__PURE__ */ React4.createElement(View3, { style: [styles2.checkbox, criteriaResults[idx] === true && styles2.checkboxChecked] }, criteriaResults[idx] === true && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.checkmark }, "\u2713")),
|
|
14501
|
+
/* @__PURE__ */ React4.createElement(Text2, { style: [styles2.checklistText, criteriaResults[idx] === true && styles2.checklistTextDone] }, step.action)
|
|
14502
|
+
)), Object.keys(criteriaResults).length > 0 && /* @__PURE__ */ React4.createElement(TouchableOpacity2, { onPress: () => setCriteriaResults({}), style: styles2.resetRow }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.resetText }, "\u21BA Reset"))), template === "rubric" && steps.map((step, idx) => /* @__PURE__ */ React4.createElement(View3, { key: idx, style: styles2.rubricItem }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.rubricTitle }, idx + 1, ". ", step.action), step.expectedResult && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.rubricExpected }, step.expectedResult), rubricMode === "pass_fail" ? /* @__PURE__ */ React4.createElement(View3, { style: styles2.passFailRow }, /* @__PURE__ */ React4.createElement(
|
|
14069
14503
|
TouchableOpacity2,
|
|
14070
14504
|
{
|
|
14071
14505
|
onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: true })),
|
|
14072
14506
|
style: [styles2.pfButton, criteriaResults[idx] === true && styles2.pfButtonPass]
|
|
14073
14507
|
},
|
|
14074
|
-
/* @__PURE__ */
|
|
14075
|
-
), /* @__PURE__ */
|
|
14508
|
+
/* @__PURE__ */ React4.createElement(Text2, { style: [styles2.pfButtonText, criteriaResults[idx] === true && styles2.pfButtonTextActive] }, "\u2713 Pass")
|
|
14509
|
+
), /* @__PURE__ */ React4.createElement(
|
|
14076
14510
|
TouchableOpacity2,
|
|
14077
14511
|
{
|
|
14078
14512
|
onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: false })),
|
|
14079
14513
|
style: [styles2.pfButton, criteriaResults[idx] === false && styles2.pfButtonFail]
|
|
14080
14514
|
},
|
|
14081
|
-
/* @__PURE__ */
|
|
14082
|
-
)) : /* @__PURE__ */
|
|
14515
|
+
/* @__PURE__ */ React4.createElement(Text2, { style: [styles2.pfButtonText, criteriaResults[idx] === false && styles2.pfButtonTextActive] }, "\u2717 Fail")
|
|
14516
|
+
)) : /* @__PURE__ */ React4.createElement(View3, { style: styles2.ratingRow }, [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ React4.createElement(
|
|
14083
14517
|
TouchableOpacity2,
|
|
14084
14518
|
{
|
|
14085
14519
|
key: n,
|
|
14086
14520
|
onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: n })),
|
|
14087
14521
|
style: [styles2.ratingBtn, criteriaResults[idx] === n && styles2.ratingBtnActive]
|
|
14088
14522
|
},
|
|
14089
|
-
/* @__PURE__ */
|
|
14090
|
-
))))), template === "freeform" && /* @__PURE__ */
|
|
14523
|
+
/* @__PURE__ */ React4.createElement(Text2, { style: [styles2.ratingBtnText, criteriaResults[idx] === n && styles2.ratingBtnTextActive] }, n)
|
|
14524
|
+
))))), template === "freeform" && /* @__PURE__ */ React4.createElement(View3, { style: styles2.freeformBox }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.freeformText }, "Review the area and note:"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.freeformBullet }, "\u2022 What works well"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.freeformBullet }, "\u2022 Issues or concerns"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.freeformBullet }, "\u2022 Suggestions"))), testCase.expectedResult && /* @__PURE__ */ React4.createElement(View3, { style: styles2.expectedBox }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.expectedLabel }, "\u2705 Expected Result"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.expectedText }, testCase.expectedResult)), testCase.targetRoute && onNavigate && /* @__PURE__ */ React4.createElement(
|
|
14091
14525
|
TouchableOpacity2,
|
|
14092
14526
|
{
|
|
14093
14527
|
style: styles2.navigateButton,
|
|
@@ -14097,21 +14531,21 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
14097
14531
|
nav.closeWidget?.();
|
|
14098
14532
|
}
|
|
14099
14533
|
},
|
|
14100
|
-
/* @__PURE__ */
|
|
14101
|
-
), /* @__PURE__ */
|
|
14534
|
+
/* @__PURE__ */ React4.createElement(Text2, { style: styles2.navigateText }, "\u{1F9ED} Go to test location")
|
|
14535
|
+
), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { onPress: () => setShowDetails(!showDetails), style: styles2.detailsToggle }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ React4.createElement(View3, { style: styles2.detailsSection }, testCase.key && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.detailMeta }, testCase.key, " \xB7 ", testCase.priority, " \xB7 ", info.name), testCase.description && /* @__PURE__ */ React4.createElement(Text2, { style: styles2.detailDesc }, testCase.description), testCase.group && /* @__PURE__ */ React4.createElement(View3, { style: styles2.folderProgress }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.folderName }, "\u{1F4C1} ", testCase.group.name))), /* @__PURE__ */ React4.createElement(View3, { style: styles2.actionButtons }, /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.failBtn, isSubmitting && { opacity: 0.5 }], onPress: handleFail, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.failBtnText }, isSubmitting ? "Failing..." : "Fail")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.skipBtn, isSubmitting && { opacity: 0.5 }], onPress: () => setShowSkipModal(true), disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.skipBtnText }, "Skip")), /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: [styles2.actionBtn, styles2.passBtn, isSubmitting && { opacity: 0.5 }], onPress: handlePass, disabled: isSubmitting }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.passBtnText }, isSubmitting ? "Passing..." : "Pass"))), /* @__PURE__ */ React4.createElement(Modal, { visible: showSkipModal, transparent: true, animationType: "fade" }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.modalOverlay }, /* @__PURE__ */ React4.createElement(View3, { style: styles2.modalContent }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.modalTitle }, "Skip this test?"), /* @__PURE__ */ React4.createElement(Text2, { style: styles2.modalSubtitle }, "Select a reason:"), [
|
|
14102
14536
|
{ reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
|
|
14103
14537
|
{ reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
|
|
14104
14538
|
{ reason: "dependency", label: "\u{1F517} Needs another test first" },
|
|
14105
14539
|
{ reason: "other", label: "\u{1F4DD} Other reason" }
|
|
14106
|
-
].map(({ reason, label }) => /* @__PURE__ */
|
|
14540
|
+
].map(({ reason, label }) => /* @__PURE__ */ React4.createElement(
|
|
14107
14541
|
TouchableOpacity2,
|
|
14108
14542
|
{
|
|
14109
14543
|
key: reason,
|
|
14110
14544
|
style: [styles2.skipOption, selectedSkipReason === reason && styles2.skipOptionActive],
|
|
14111
14545
|
onPress: () => setSelectedSkipReason(reason)
|
|
14112
14546
|
},
|
|
14113
|
-
/* @__PURE__ */
|
|
14114
|
-
)), /* @__PURE__ */
|
|
14547
|
+
/* @__PURE__ */ React4.createElement(Text2, { style: [styles2.skipOptionText, selectedSkipReason === reason && styles2.skipOptionTextActive] }, label)
|
|
14548
|
+
)), /* @__PURE__ */ React4.createElement(
|
|
14115
14549
|
TextInput,
|
|
14116
14550
|
{
|
|
14117
14551
|
style: styles2.skipNotes,
|
|
@@ -14121,21 +14555,21 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
14121
14555
|
placeholderTextColor: colors.textMuted,
|
|
14122
14556
|
multiline: true
|
|
14123
14557
|
}
|
|
14124
|
-
), /* @__PURE__ */
|
|
14558
|
+
), /* @__PURE__ */ React4.createElement(View3, { style: styles2.skipActions }, /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: styles2.skipCancel, onPress: () => {
|
|
14125
14559
|
setShowSkipModal(false);
|
|
14126
14560
|
setSelectedSkipReason(null);
|
|
14127
14561
|
setSkipNotes("");
|
|
14128
|
-
} }, /* @__PURE__ */
|
|
14562
|
+
} }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.skipCancelText }, "Cancel")), /* @__PURE__ */ React4.createElement(
|
|
14129
14563
|
TouchableOpacity2,
|
|
14130
14564
|
{
|
|
14131
14565
|
style: [styles2.skipConfirm, !selectedSkipReason && { opacity: 0.4 }],
|
|
14132
14566
|
onPress: handleSkip,
|
|
14133
14567
|
disabled: !selectedSkipReason || skipping
|
|
14134
14568
|
},
|
|
14135
|
-
/* @__PURE__ */
|
|
14569
|
+
/* @__PURE__ */ React4.createElement(Text2, { style: styles2.skipConfirmText }, skipping ? "Skipping..." : "Skip Test")
|
|
14136
14570
|
))))));
|
|
14137
14571
|
}
|
|
14138
|
-
var styles2 =
|
|
14572
|
+
var styles2 = StyleSheet4.create({
|
|
14139
14573
|
container: { paddingBottom: 16 },
|
|
14140
14574
|
retestBanner: { flexDirection: "row", alignItems: "center", gap: 6, backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 8, paddingVertical: 6, paddingHorizontal: 10, marginBottom: 10 },
|
|
14141
14575
|
retestIcon: { fontSize: 14 },
|
|
@@ -14230,14 +14664,17 @@ var styles2 = StyleSheet3.create({
|
|
|
14230
14664
|
});
|
|
14231
14665
|
|
|
14232
14666
|
// src/widget/screens/TestListScreen.tsx
|
|
14233
|
-
import
|
|
14234
|
-
import { View as
|
|
14667
|
+
import React5, { useState as useState3, useMemo as useMemo2, useCallback as useCallback3, useEffect as useEffect5 } from "react";
|
|
14668
|
+
import { View as View4, Text as Text3, TouchableOpacity as TouchableOpacity3, StyleSheet as StyleSheet5, ScrollView, TextInput as TextInput2 } from "react-native";
|
|
14235
14669
|
function TestListScreen({ nav }) {
|
|
14236
|
-
const { assignments, currentAssignment, refreshAssignments } = useBugBear();
|
|
14670
|
+
const { assignments, currentAssignment, refreshAssignments, isLoading } = useBugBear();
|
|
14237
14671
|
const [filter, setFilter] = useState3("all");
|
|
14238
14672
|
const [roleFilter, setRoleFilter] = useState3(null);
|
|
14673
|
+
const [trackFilter, setTrackFilter] = useState3(null);
|
|
14674
|
+
const [searchQuery, setSearchQuery] = useState3("");
|
|
14675
|
+
const [sortMode, setSortMode] = useState3("priority");
|
|
14239
14676
|
const [collapsedFolders, setCollapsedFolders] = useState3(/* @__PURE__ */ new Set());
|
|
14240
|
-
|
|
14677
|
+
useEffect5(() => {
|
|
14241
14678
|
refreshAssignments();
|
|
14242
14679
|
}, []);
|
|
14243
14680
|
const availableRoles = useMemo2(() => {
|
|
@@ -14247,6 +14684,13 @@ function TestListScreen({ nav }) {
|
|
|
14247
14684
|
}
|
|
14248
14685
|
return Array.from(roleMap.values());
|
|
14249
14686
|
}, [assignments]);
|
|
14687
|
+
const availableTracks = useMemo2(() => {
|
|
14688
|
+
const trackMap = /* @__PURE__ */ new Map();
|
|
14689
|
+
for (const a of assignments) {
|
|
14690
|
+
if (a.testCase.track) trackMap.set(a.testCase.track.id, a.testCase.track);
|
|
14691
|
+
}
|
|
14692
|
+
return Array.from(trackMap.values());
|
|
14693
|
+
}, [assignments]);
|
|
14250
14694
|
const selectedRole = availableRoles.find((r) => r.id === roleFilter);
|
|
14251
14695
|
const groupedAssignments = useMemo2(() => {
|
|
14252
14696
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -14270,6 +14714,8 @@ function TestListScreen({ nav }) {
|
|
|
14270
14714
|
folder.assignments.sort((a, b) => {
|
|
14271
14715
|
if (a.isVerification && !b.isVerification) return -1;
|
|
14272
14716
|
if (!a.isVerification && b.isVerification) return 1;
|
|
14717
|
+
if (sortMode === "alpha") return a.testCase.title.localeCompare(b.testCase.title);
|
|
14718
|
+
if (sortMode === "recent") return 0;
|
|
14273
14719
|
const sd = (statusOrder[a.status] ?? 5) - (statusOrder[b.status] ?? 5);
|
|
14274
14720
|
if (sd !== 0) return sd;
|
|
14275
14721
|
return (priorityOrder[a.testCase.priority] ?? 4) - (priorityOrder[b.testCase.priority] ?? 4);
|
|
@@ -14281,7 +14727,7 @@ function TestListScreen({ nav }) {
|
|
|
14281
14727
|
if (!b.group) return -1;
|
|
14282
14728
|
return a.group.sortOrder - b.group.sortOrder;
|
|
14283
14729
|
});
|
|
14284
|
-
}, [assignments]);
|
|
14730
|
+
}, [assignments, sortMode]);
|
|
14285
14731
|
const toggleFolder = useCallback3((id) => {
|
|
14286
14732
|
setCollapsedFolders((prev) => {
|
|
14287
14733
|
const next = new Set(prev);
|
|
@@ -14290,28 +14736,36 @@ function TestListScreen({ nav }) {
|
|
|
14290
14736
|
return next;
|
|
14291
14737
|
});
|
|
14292
14738
|
}, []);
|
|
14293
|
-
const filterAssignment = (a) => {
|
|
14739
|
+
const filterAssignment = useCallback3((a) => {
|
|
14294
14740
|
if (roleFilter && a.testCase.role?.id !== roleFilter) return false;
|
|
14741
|
+
if (trackFilter && a.testCase.track?.id !== trackFilter) return false;
|
|
14742
|
+
if (searchQuery) {
|
|
14743
|
+
const q = searchQuery.toLowerCase();
|
|
14744
|
+
const titleMatch = a.testCase.title.toLowerCase().includes(q);
|
|
14745
|
+
const keyMatch = a.testCase.testKey.toLowerCase().includes(q);
|
|
14746
|
+
if (!titleMatch && !keyMatch) return false;
|
|
14747
|
+
}
|
|
14295
14748
|
if (filter === "pending") return a.status === "pending" || a.status === "in_progress";
|
|
14296
14749
|
if (filter === "done") return a.status === "passed";
|
|
14297
14750
|
if (filter === "reopened") return a.status === "failed";
|
|
14298
14751
|
return true;
|
|
14299
|
-
};
|
|
14300
|
-
|
|
14752
|
+
}, [roleFilter, trackFilter, searchQuery, filter]);
|
|
14753
|
+
if (isLoading) return /* @__PURE__ */ React5.createElement(TestListScreenSkeleton, null);
|
|
14754
|
+
return /* @__PURE__ */ React5.createElement(View4, null, /* @__PURE__ */ React5.createElement(View4, { style: styles3.filterBar }, [
|
|
14301
14755
|
{ key: "all", label: "All", count: assignments.length },
|
|
14302
14756
|
{ key: "pending", label: "To Do", count: assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length },
|
|
14303
14757
|
{ key: "done", label: "Done", count: assignments.filter((a) => a.status === "passed").length },
|
|
14304
14758
|
{ key: "reopened", label: "Re Opened", count: assignments.filter((a) => a.status === "failed").length }
|
|
14305
|
-
].map((f) => /* @__PURE__ */
|
|
14759
|
+
].map((f) => /* @__PURE__ */ React5.createElement(TouchableOpacity3, { key: f.key, style: [styles3.filterBtn, filter === f.key && styles3.filterBtnActive], onPress: () => setFilter(f.key) }, /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.filterBtnText, filter === f.key && styles3.filterBtnTextActive] }, f.label, " (", f.count, ")")))), availableRoles.length >= 2 && /* @__PURE__ */ React5.createElement(View4, { style: styles3.roleSection }, /* @__PURE__ */ React5.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles3.roleBar }, /* @__PURE__ */ React5.createElement(
|
|
14306
14760
|
TouchableOpacity3,
|
|
14307
14761
|
{
|
|
14308
14762
|
style: [styles3.roleBtn, !roleFilter && styles3.roleBtnActive],
|
|
14309
14763
|
onPress: () => setRoleFilter(null)
|
|
14310
14764
|
},
|
|
14311
|
-
/* @__PURE__ */
|
|
14765
|
+
/* @__PURE__ */ React5.createElement(Text3, { style: [styles3.roleBtnText, !roleFilter && styles3.roleBtnTextActive] }, "All Roles")
|
|
14312
14766
|
), availableRoles.map((role) => {
|
|
14313
14767
|
const isActive = roleFilter === role.id;
|
|
14314
|
-
return /* @__PURE__ */
|
|
14768
|
+
return /* @__PURE__ */ React5.createElement(
|
|
14315
14769
|
TouchableOpacity3,
|
|
14316
14770
|
{
|
|
14317
14771
|
key: role.id,
|
|
@@ -14321,33 +14775,72 @@ function TestListScreen({ nav }) {
|
|
|
14321
14775
|
],
|
|
14322
14776
|
onPress: () => setRoleFilter(isActive ? null : role.id)
|
|
14323
14777
|
},
|
|
14324
|
-
/* @__PURE__ */
|
|
14325
|
-
/* @__PURE__ */
|
|
14778
|
+
/* @__PURE__ */ React5.createElement(View4, { style: [styles3.roleDot, { backgroundColor: role.color }] }),
|
|
14779
|
+
/* @__PURE__ */ React5.createElement(Text3, { style: [styles3.roleBtnText, isActive && { color: role.color, fontWeight: "600" }] }, role.name)
|
|
14326
14780
|
);
|
|
14327
|
-
})), selectedRole?.loginHint && /* @__PURE__ */
|
|
14781
|
+
})), selectedRole?.loginHint && /* @__PURE__ */ React5.createElement(View4, { style: [styles3.loginHint, { backgroundColor: selectedRole.color + "10", borderColor: selectedRole.color + "30" }] }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.loginHintText }, "Log in as: ", selectedRole.loginHint))), /* @__PURE__ */ React5.createElement(View4, { style: styles3.searchContainer }, /* @__PURE__ */ React5.createElement(
|
|
14782
|
+
TextInput2,
|
|
14783
|
+
{
|
|
14784
|
+
value: searchQuery,
|
|
14785
|
+
onChangeText: setSearchQuery,
|
|
14786
|
+
placeholder: "Search tests...",
|
|
14787
|
+
placeholderTextColor: colors.textMuted,
|
|
14788
|
+
style: styles3.searchInput
|
|
14789
|
+
}
|
|
14790
|
+
)), /* @__PURE__ */ React5.createElement(View4, { style: styles3.trackSortRow }, availableTracks.length >= 2 && /* @__PURE__ */ React5.createElement(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: { flex: 1 } }, /* @__PURE__ */ React5.createElement(
|
|
14791
|
+
TouchableOpacity3,
|
|
14792
|
+
{
|
|
14793
|
+
style: [styles3.trackBtn, !trackFilter && styles3.trackBtnActive],
|
|
14794
|
+
onPress: () => setTrackFilter(null)
|
|
14795
|
+
},
|
|
14796
|
+
/* @__PURE__ */ React5.createElement(Text3, { style: [styles3.trackBtnText, !trackFilter && styles3.trackBtnTextActive] }, "All Tracks")
|
|
14797
|
+
), availableTracks.map((track) => {
|
|
14798
|
+
const isActive = trackFilter === track.id;
|
|
14799
|
+
return /* @__PURE__ */ React5.createElement(
|
|
14800
|
+
TouchableOpacity3,
|
|
14801
|
+
{
|
|
14802
|
+
key: track.id,
|
|
14803
|
+
style: [styles3.trackBtn, isActive && { backgroundColor: track.color + "20", borderColor: track.color + "60", borderWidth: 1 }],
|
|
14804
|
+
onPress: () => setTrackFilter(isActive ? null : track.id)
|
|
14805
|
+
},
|
|
14806
|
+
/* @__PURE__ */ React5.createElement(Text3, { style: [styles3.trackBtnText, isActive && { color: track.color, fontWeight: "600" }] }, track.icon, " ", track.name)
|
|
14807
|
+
);
|
|
14808
|
+
})), /* @__PURE__ */ React5.createElement(View4, { style: styles3.sortGroup }, [
|
|
14809
|
+
{ key: "priority", label: "\u2195" },
|
|
14810
|
+
{ key: "recent", label: "\u{1F550}" },
|
|
14811
|
+
{ key: "alpha", label: "AZ" }
|
|
14812
|
+
].map((s2) => /* @__PURE__ */ React5.createElement(
|
|
14813
|
+
TouchableOpacity3,
|
|
14814
|
+
{
|
|
14815
|
+
key: s2.key,
|
|
14816
|
+
style: [styles3.sortBtn, sortMode === s2.key && styles3.sortBtnActive],
|
|
14817
|
+
onPress: () => setSortMode(s2.key)
|
|
14818
|
+
},
|
|
14819
|
+
/* @__PURE__ */ React5.createElement(Text3, { style: [styles3.sortBtnText, sortMode === s2.key && styles3.sortBtnTextActive] }, s2.label)
|
|
14820
|
+
)))), groupedAssignments.map((folder) => {
|
|
14328
14821
|
const folderId = folder.group?.id || "ungrouped";
|
|
14329
14822
|
const isCollapsed = collapsedFolders.has(folderId);
|
|
14330
14823
|
const filtered = folder.assignments.filter(filterAssignment);
|
|
14331
14824
|
if (filtered.length === 0 && filter !== "all") return null;
|
|
14332
|
-
return /* @__PURE__ */
|
|
14825
|
+
return /* @__PURE__ */ React5.createElement(View4, { key: folderId, style: styles3.folder }, /* @__PURE__ */ React5.createElement(TouchableOpacity3, { style: styles3.folderHeader, onPress: () => toggleFolder(folderId) }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.folderToggle }, isCollapsed ? "\u25B6" : "\u25BC"), /* @__PURE__ */ React5.createElement(Text3, { style: styles3.folderName, numberOfLines: 1 }, folder.group?.name || "Ungrouped"), /* @__PURE__ */ React5.createElement(View4, { style: styles3.folderProgress }, /* @__PURE__ */ React5.createElement(View4, { style: [styles3.folderProgressFill, { width: `${folder.stats.total > 0 ? Math.round((folder.stats.passed + folder.stats.failed) / folder.stats.total * 100) : 0}%` }] })), /* @__PURE__ */ React5.createElement(Text3, { style: styles3.folderCount }, folder.stats.passed + folder.stats.failed, "/", folder.stats.total)), !isCollapsed && filtered.map((assignment) => {
|
|
14333
14826
|
const badge = getStatusBadge(assignment.status);
|
|
14334
14827
|
const isCurrent = currentAssignment?.id === assignment.id;
|
|
14335
|
-
return /* @__PURE__ */
|
|
14828
|
+
return /* @__PURE__ */ React5.createElement(
|
|
14336
14829
|
TouchableOpacity3,
|
|
14337
14830
|
{
|
|
14338
14831
|
key: assignment.id,
|
|
14339
14832
|
style: [styles3.testItem, isCurrent && styles3.testItemCurrent],
|
|
14340
14833
|
onPress: () => nav.push({ name: "TEST_DETAIL", testId: assignment.id })
|
|
14341
14834
|
},
|
|
14342
|
-
/* @__PURE__ */
|
|
14343
|
-
/* @__PURE__ */
|
|
14344
|
-
/* @__PURE__ */
|
|
14835
|
+
/* @__PURE__ */ React5.createElement(Text3, { style: styles3.testBadge }, badge.icon),
|
|
14836
|
+
/* @__PURE__ */ React5.createElement(View4, { style: styles3.testInfo }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.testTitle, numberOfLines: 1 }, assignment.testCase.title), /* @__PURE__ */ React5.createElement(View4, { style: styles3.testMetaRow }, assignment.isVerification && /* @__PURE__ */ React5.createElement(View4, { style: styles3.retestTag }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.retestTagText }, "Retest")), /* @__PURE__ */ React5.createElement(Text3, { style: styles3.testMeta }, assignment.testCase.testKey, " \xB7 ", assignment.testCase.priority), assignment.testCase.role && /* @__PURE__ */ React5.createElement(View4, { style: styles3.roleBadgeRow }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.testMeta }, " \xB7 "), /* @__PURE__ */ React5.createElement(View4, { style: [styles3.roleBadgeDot, { backgroundColor: assignment.testCase.role.color }] }), /* @__PURE__ */ React5.createElement(Text3, { style: [styles3.testMeta, { color: assignment.testCase.role.color, fontWeight: "500" }] }, assignment.testCase.role.name)))),
|
|
14837
|
+
/* @__PURE__ */ React5.createElement(View4, { style: [
|
|
14345
14838
|
styles3.statusPill,
|
|
14346
14839
|
{
|
|
14347
14840
|
backgroundColor: assignment.status === "passed" ? "#14532d" : assignment.status === "failed" ? "#450a0a" : assignment.status === "in_progress" ? "#172554" : "#27272a",
|
|
14348
14841
|
borderColor: assignment.status === "passed" ? "#166534" : assignment.status === "failed" ? "#7f1d1d" : assignment.status === "in_progress" ? "#1e3a5f" : "#3f3f46"
|
|
14349
14842
|
}
|
|
14350
|
-
] }, /* @__PURE__ */
|
|
14843
|
+
] }, /* @__PURE__ */ React5.createElement(Text3, { style: [
|
|
14351
14844
|
styles3.statusPillText,
|
|
14352
14845
|
{
|
|
14353
14846
|
color: assignment.status === "passed" ? "#4ade80" : assignment.status === "failed" ? "#f87171" : assignment.status === "in_progress" ? "#60a5fa" : "#d4d4d8"
|
|
@@ -14355,9 +14848,9 @@ function TestListScreen({ nav }) {
|
|
|
14355
14848
|
] }, badge.label))
|
|
14356
14849
|
);
|
|
14357
14850
|
}));
|
|
14358
|
-
}), /* @__PURE__ */
|
|
14851
|
+
}), /* @__PURE__ */ React5.createElement(TouchableOpacity3, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.refreshText }, "\u21BB", " Refresh")));
|
|
14359
14852
|
}
|
|
14360
|
-
var styles3 =
|
|
14853
|
+
var styles3 = StyleSheet5.create({
|
|
14361
14854
|
filterBar: { flexDirection: "row", gap: 8, marginBottom: 8 },
|
|
14362
14855
|
filterBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
|
|
14363
14856
|
filterBtnActive: { backgroundColor: colors.blue, borderColor: colors.blue },
|
|
@@ -14392,13 +14885,25 @@ var styles3 = StyleSheet4.create({
|
|
|
14392
14885
|
testMeta: { fontSize: 11, color: colors.textDim },
|
|
14393
14886
|
statusPill: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 6, borderWidth: 1, marginLeft: 8 },
|
|
14394
14887
|
statusPillText: { fontSize: 10, fontWeight: "600" },
|
|
14888
|
+
searchContainer: { marginBottom: 8 },
|
|
14889
|
+
searchInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
|
|
14890
|
+
trackSortRow: { flexDirection: "row", alignItems: "center", marginBottom: 10, gap: 8 },
|
|
14891
|
+
trackBtn: { flexDirection: "row", alignItems: "center", gap: 4, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, marginRight: 6, borderWidth: 1, borderColor: "transparent" },
|
|
14892
|
+
trackBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
|
|
14893
|
+
trackBtnText: { fontSize: 11, color: colors.textMuted },
|
|
14894
|
+
trackBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
|
|
14895
|
+
sortGroup: { flexDirection: "row", gap: 2 },
|
|
14896
|
+
sortBtn: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 6, borderWidth: 1, borderColor: "transparent" },
|
|
14897
|
+
sortBtnActive: { backgroundColor: colors.card, borderColor: colors.border },
|
|
14898
|
+
sortBtnText: { fontSize: 11, color: colors.textMuted },
|
|
14899
|
+
sortBtnTextActive: { color: colors.textPrimary, fontWeight: "600" },
|
|
14395
14900
|
refreshBtn: { alignItems: "center", paddingVertical: 12 },
|
|
14396
14901
|
refreshText: { fontSize: 13, color: colors.blue }
|
|
14397
14902
|
});
|
|
14398
14903
|
|
|
14399
14904
|
// src/widget/screens/TestFeedbackScreen.tsx
|
|
14400
|
-
import
|
|
14401
|
-
import { View as
|
|
14905
|
+
import React8, { useState as useState5 } from "react";
|
|
14906
|
+
import { View as View7, Text as Text6, TouchableOpacity as TouchableOpacity6, TextInput as TextInput3, StyleSheet as StyleSheet8 } from "react-native";
|
|
14402
14907
|
|
|
14403
14908
|
// src/widget/useImageAttachments.ts
|
|
14404
14909
|
import { useState as useState4, useCallback as useCallback4 } from "react";
|
|
@@ -14498,17 +15003,17 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
|
|
|
14498
15003
|
}
|
|
14499
15004
|
|
|
14500
15005
|
// src/widget/ImagePickerButtons.tsx
|
|
14501
|
-
import
|
|
14502
|
-
import { View as
|
|
15006
|
+
import React7 from "react";
|
|
15007
|
+
import { View as View6, Text as Text5, TouchableOpacity as TouchableOpacity5, StyleSheet as StyleSheet7 } from "react-native";
|
|
14503
15008
|
|
|
14504
15009
|
// src/widget/ImagePreviewStrip.tsx
|
|
14505
|
-
import
|
|
14506
|
-
import { View as
|
|
15010
|
+
import React6 from "react";
|
|
15011
|
+
import { View as View5, Text as Text4, TouchableOpacity as TouchableOpacity4, ScrollView as ScrollView2, Image, ActivityIndicator, StyleSheet as StyleSheet6 } from "react-native";
|
|
14507
15012
|
function ImagePreviewStrip({ images, onRemove }) {
|
|
14508
15013
|
if (images.length === 0) return null;
|
|
14509
|
-
return /* @__PURE__ */
|
|
15014
|
+
return /* @__PURE__ */ React6.createElement(ScrollView2, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles4.strip }, images.map((img) => /* @__PURE__ */ React6.createElement(View5, { key: img.id, style: styles4.thumbContainer }, /* @__PURE__ */ React6.createElement(Image, { source: { uri: img.localUri }, style: styles4.thumb }), img.status === "uploading" && /* @__PURE__ */ React6.createElement(View5, { style: styles4.thumbOverlay }, /* @__PURE__ */ React6.createElement(ActivityIndicator, { size: "small", color: "#fff" })), img.status === "error" && /* @__PURE__ */ React6.createElement(View5, { style: [styles4.thumbOverlay, styles4.thumbOverlayError] }, /* @__PURE__ */ React6.createElement(Text4, { style: styles4.thumbErrorText }, "!")), /* @__PURE__ */ React6.createElement(TouchableOpacity4, { style: styles4.thumbRemove, onPress: () => onRemove(img.id) }, /* @__PURE__ */ React6.createElement(Text4, { style: styles4.thumbRemoveText }, "\u2715")))));
|
|
14510
15015
|
}
|
|
14511
|
-
var styles4 =
|
|
15016
|
+
var styles4 = StyleSheet6.create({
|
|
14512
15017
|
strip: {
|
|
14513
15018
|
flexDirection: "row",
|
|
14514
15019
|
marginTop: 4
|
|
@@ -14527,7 +15032,7 @@ var styles4 = StyleSheet5.create({
|
|
|
14527
15032
|
borderRadius: 8
|
|
14528
15033
|
},
|
|
14529
15034
|
thumbOverlay: {
|
|
14530
|
-
...
|
|
15035
|
+
...StyleSheet6.absoluteFillObject,
|
|
14531
15036
|
backgroundColor: "rgba(0,0,0,0.5)",
|
|
14532
15037
|
justifyContent: "center",
|
|
14533
15038
|
alignItems: "center",
|
|
@@ -14562,25 +15067,25 @@ var styles4 = StyleSheet5.create({
|
|
|
14562
15067
|
// src/widget/ImagePickerButtons.tsx
|
|
14563
15068
|
function ImagePickerButtons({ images, maxImages, onPickGallery, onPickCamera, onRemove, label }) {
|
|
14564
15069
|
if (!IMAGE_PICKER_AVAILABLE) return null;
|
|
14565
|
-
return /* @__PURE__ */
|
|
15070
|
+
return /* @__PURE__ */ React7.createElement(View6, { style: styles5.section }, label && /* @__PURE__ */ React7.createElement(Text5, { style: styles5.label }, label), /* @__PURE__ */ React7.createElement(View6, { style: styles5.buttonRow }, /* @__PURE__ */ React7.createElement(
|
|
14566
15071
|
TouchableOpacity5,
|
|
14567
15072
|
{
|
|
14568
15073
|
style: styles5.pickButton,
|
|
14569
15074
|
onPress: onPickGallery,
|
|
14570
15075
|
disabled: images.length >= maxImages
|
|
14571
15076
|
},
|
|
14572
|
-
/* @__PURE__ */
|
|
14573
|
-
), /* @__PURE__ */
|
|
15077
|
+
/* @__PURE__ */ React7.createElement(Text5, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Gallery")
|
|
15078
|
+
), /* @__PURE__ */ React7.createElement(
|
|
14574
15079
|
TouchableOpacity5,
|
|
14575
15080
|
{
|
|
14576
15081
|
style: styles5.pickButton,
|
|
14577
15082
|
onPress: onPickCamera,
|
|
14578
15083
|
disabled: images.length >= maxImages
|
|
14579
15084
|
},
|
|
14580
|
-
/* @__PURE__ */
|
|
14581
|
-
), /* @__PURE__ */
|
|
15085
|
+
/* @__PURE__ */ React7.createElement(Text5, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Camera")
|
|
15086
|
+
), /* @__PURE__ */ React7.createElement(Text5, { style: styles5.countText }, images.length, "/", maxImages)), /* @__PURE__ */ React7.createElement(ImagePreviewStrip, { images, onRemove }));
|
|
14582
15087
|
}
|
|
14583
|
-
var styles5 =
|
|
15088
|
+
var styles5 = StyleSheet7.create({
|
|
14584
15089
|
section: {
|
|
14585
15090
|
marginTop: 12,
|
|
14586
15091
|
marginBottom: 4
|
|
@@ -14630,6 +15135,7 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
|
|
|
14630
15135
|
const assignment = assignments.find((a) => a.id === assignmentId);
|
|
14631
15136
|
const showFlags = rating < 4;
|
|
14632
15137
|
const handleSubmit = async () => {
|
|
15138
|
+
if (submitting || images.isUploading) return;
|
|
14633
15139
|
setSubmitting(true);
|
|
14634
15140
|
if (client && assignment) {
|
|
14635
15141
|
const screenshotUrls = images.getScreenshotUrls();
|
|
@@ -14680,22 +15186,22 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
|
|
|
14680
15186
|
}
|
|
14681
15187
|
}
|
|
14682
15188
|
};
|
|
14683
|
-
return /* @__PURE__ */
|
|
15189
|
+
return /* @__PURE__ */ React8.createElement(View7, { style: styles6.container }, /* @__PURE__ */ React8.createElement(Text6, { style: styles6.header }, status === "passed" ? "\u2705 Test Passed!" : "\u274C Test Failed"), /* @__PURE__ */ React8.createElement(Text6, { style: styles6.subheader }, "Rate this test case"), /* @__PURE__ */ React8.createElement(View7, { style: styles6.starRow }, [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ React8.createElement(TouchableOpacity6, { key: n, onPress: () => setRating(n), style: styles6.starButton }, /* @__PURE__ */ React8.createElement(Text6, { style: [styles6.star, n <= rating && styles6.starActive] }, n <= rating ? "\u2605" : "\u2606")))), showFlags && /* @__PURE__ */ React8.createElement(View7, { style: styles6.flagsSection }, /* @__PURE__ */ React8.createElement(Text6, { style: styles6.flagsLabel }, "What could be improved?"), [
|
|
14684
15190
|
{ key: "isOutdated", label: "Test is outdated" },
|
|
14685
15191
|
{ key: "needsMoreDetail", label: "Needs more detail" },
|
|
14686
15192
|
{ key: "stepsUnclear", label: "Steps are unclear" },
|
|
14687
15193
|
{ key: "expectedResultUnclear", label: "Expected result unclear" }
|
|
14688
|
-
].map(({ key, label }) => /* @__PURE__ */
|
|
15194
|
+
].map(({ key, label }) => /* @__PURE__ */ React8.createElement(
|
|
14689
15195
|
TouchableOpacity6,
|
|
14690
15196
|
{
|
|
14691
15197
|
key,
|
|
14692
15198
|
style: [styles6.flagItem, flags[key] && styles6.flagItemActive],
|
|
14693
15199
|
onPress: () => setFlags((prev) => ({ ...prev, [key]: !prev[key] }))
|
|
14694
15200
|
},
|
|
14695
|
-
/* @__PURE__ */
|
|
14696
|
-
/* @__PURE__ */
|
|
14697
|
-
))), /* @__PURE__ */
|
|
14698
|
-
|
|
15201
|
+
/* @__PURE__ */ React8.createElement(View7, { style: [styles6.flagCheck, flags[key] && styles6.flagCheckActive] }, flags[key] && /* @__PURE__ */ React8.createElement(Text6, { style: styles6.flagCheckmark }, "\u2713")),
|
|
15202
|
+
/* @__PURE__ */ React8.createElement(Text6, { style: [styles6.flagText, flags[key] && styles6.flagTextActive] }, label)
|
|
15203
|
+
))), /* @__PURE__ */ React8.createElement(
|
|
15204
|
+
TextInput3,
|
|
14699
15205
|
{
|
|
14700
15206
|
style: styles6.noteInput,
|
|
14701
15207
|
value: note,
|
|
@@ -14704,7 +15210,7 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
|
|
|
14704
15210
|
placeholderTextColor: colors.textMuted,
|
|
14705
15211
|
multiline: true
|
|
14706
15212
|
}
|
|
14707
|
-
), /* @__PURE__ */
|
|
15213
|
+
), /* @__PURE__ */ React8.createElement(
|
|
14708
15214
|
ImagePickerButtons,
|
|
14709
15215
|
{
|
|
14710
15216
|
images: images.images,
|
|
@@ -14714,9 +15220,9 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
|
|
|
14714
15220
|
onRemove: images.removeImage,
|
|
14715
15221
|
label: "Screenshots (optional)"
|
|
14716
15222
|
}
|
|
14717
|
-
), /* @__PURE__ */
|
|
15223
|
+
), /* @__PURE__ */ React8.createElement(View7, { style: styles6.actions }, /* @__PURE__ */ React8.createElement(TouchableOpacity6, { style: styles6.skipButton, onPress: handleSkip }, /* @__PURE__ */ React8.createElement(Text6, { style: styles6.skipText }, "Skip")), /* @__PURE__ */ React8.createElement(TouchableOpacity6, { style: [shared.primaryButton, { flex: 2, opacity: submitting || images.isUploading ? 0.5 : 1 }], onPress: handleSubmit, disabled: submitting || images.isUploading }, /* @__PURE__ */ React8.createElement(Text6, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : submitting ? "Submitting..." : "Submit"))));
|
|
14718
15224
|
}
|
|
14719
|
-
var styles6 =
|
|
15225
|
+
var styles6 = StyleSheet8.create({
|
|
14720
15226
|
container: { paddingTop: 8 },
|
|
14721
15227
|
header: { fontSize: 22, fontWeight: "700", color: colors.textPrimary, textAlign: "center", marginBottom: 4 },
|
|
14722
15228
|
subheader: { fontSize: 14, color: colors.textMuted, textAlign: "center", marginBottom: 20 },
|
|
@@ -14740,12 +15246,12 @@ var styles6 = StyleSheet7.create({
|
|
|
14740
15246
|
});
|
|
14741
15247
|
|
|
14742
15248
|
// src/widget/screens/ReportScreen.tsx
|
|
14743
|
-
import
|
|
14744
|
-
import { View as
|
|
15249
|
+
import React10, { useState as useState7, useRef as useRef3, useEffect as useEffect6 } from "react";
|
|
15250
|
+
import { View as View9, Text as Text8, TouchableOpacity as TouchableOpacity8, TextInput as TextInput4, StyleSheet as StyleSheet10 } from "react-native";
|
|
14745
15251
|
|
|
14746
15252
|
// src/widget/CategoryPicker.tsx
|
|
14747
|
-
import
|
|
14748
|
-
import { View as
|
|
15253
|
+
import React9, { useState as useState6 } from "react";
|
|
15254
|
+
import { View as View8, Text as Text7, TouchableOpacity as TouchableOpacity7, Modal as Modal2, StyleSheet as StyleSheet9 } from "react-native";
|
|
14749
15255
|
var categoryOptions = [
|
|
14750
15256
|
{ value: "ui_ux", label: "UI/UX", icon: "\u{1F3A8}" },
|
|
14751
15257
|
{ value: "functional", label: "Functional", icon: "\u2699\uFE0F" },
|
|
@@ -14760,15 +15266,15 @@ function CategoryPicker({ value, onChange, optional = true }) {
|
|
|
14760
15266
|
onChange(category);
|
|
14761
15267
|
setModalVisible(false);
|
|
14762
15268
|
};
|
|
14763
|
-
return /* @__PURE__ */
|
|
15269
|
+
return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(
|
|
14764
15270
|
TouchableOpacity7,
|
|
14765
15271
|
{
|
|
14766
15272
|
style: styles7.trigger,
|
|
14767
15273
|
onPress: () => setModalVisible(true)
|
|
14768
15274
|
},
|
|
14769
|
-
/* @__PURE__ */
|
|
14770
|
-
/* @__PURE__ */
|
|
14771
|
-
), /* @__PURE__ */
|
|
15275
|
+
/* @__PURE__ */ React9.createElement(Text7, { style: selectedOption ? styles7.triggerTextSelected : styles7.triggerTextPlaceholder }, selectedOption ? `${selectedOption.icon} ${selectedOption.label}` : optional ? "Select category (optional)" : "Select category"),
|
|
15276
|
+
/* @__PURE__ */ React9.createElement(Text7, { style: styles7.chevron }, "\u25BC")
|
|
15277
|
+
), /* @__PURE__ */ React9.createElement(
|
|
14772
15278
|
Modal2,
|
|
14773
15279
|
{
|
|
14774
15280
|
visible: modalVisible,
|
|
@@ -14776,42 +15282,42 @@ function CategoryPicker({ value, onChange, optional = true }) {
|
|
|
14776
15282
|
animationType: "fade",
|
|
14777
15283
|
onRequestClose: () => setModalVisible(false)
|
|
14778
15284
|
},
|
|
14779
|
-
/* @__PURE__ */
|
|
15285
|
+
/* @__PURE__ */ React9.createElement(
|
|
14780
15286
|
TouchableOpacity7,
|
|
14781
15287
|
{
|
|
14782
15288
|
style: styles7.overlay,
|
|
14783
15289
|
activeOpacity: 1,
|
|
14784
15290
|
onPress: () => setModalVisible(false)
|
|
14785
15291
|
},
|
|
14786
|
-
/* @__PURE__ */
|
|
15292
|
+
/* @__PURE__ */ React9.createElement(View8, { style: styles7.modal, onStartShouldSetResponder: () => true }, /* @__PURE__ */ React9.createElement(Text7, { style: styles7.modalTitle }, "Select Category"), optional && /* @__PURE__ */ React9.createElement(
|
|
14787
15293
|
TouchableOpacity7,
|
|
14788
15294
|
{
|
|
14789
15295
|
style: [styles7.option, !value && styles7.optionSelected],
|
|
14790
15296
|
onPress: () => handleSelect(null)
|
|
14791
15297
|
},
|
|
14792
|
-
/* @__PURE__ */
|
|
14793
|
-
), categoryOptions.map(({ value: optValue, label, icon }) => /* @__PURE__ */
|
|
15298
|
+
/* @__PURE__ */ React9.createElement(Text7, { style: styles7.optionText }, "\u2014 None \u2014")
|
|
15299
|
+
), categoryOptions.map(({ value: optValue, label, icon }) => /* @__PURE__ */ React9.createElement(
|
|
14794
15300
|
TouchableOpacity7,
|
|
14795
15301
|
{
|
|
14796
15302
|
key: optValue,
|
|
14797
15303
|
style: [styles7.option, value === optValue && styles7.optionSelected],
|
|
14798
15304
|
onPress: () => handleSelect(optValue)
|
|
14799
15305
|
},
|
|
14800
|
-
/* @__PURE__ */
|
|
14801
|
-
/* @__PURE__ */
|
|
14802
|
-
value === optValue && /* @__PURE__ */
|
|
14803
|
-
)), /* @__PURE__ */
|
|
15306
|
+
/* @__PURE__ */ React9.createElement(Text7, { style: styles7.optionIcon }, icon),
|
|
15307
|
+
/* @__PURE__ */ React9.createElement(Text7, { style: styles7.optionText }, label),
|
|
15308
|
+
value === optValue && /* @__PURE__ */ React9.createElement(Text7, { style: styles7.checkmark }, "\u2713")
|
|
15309
|
+
)), /* @__PURE__ */ React9.createElement(
|
|
14804
15310
|
TouchableOpacity7,
|
|
14805
15311
|
{
|
|
14806
15312
|
style: styles7.cancelButton,
|
|
14807
15313
|
onPress: () => setModalVisible(false)
|
|
14808
15314
|
},
|
|
14809
|
-
/* @__PURE__ */
|
|
15315
|
+
/* @__PURE__ */ React9.createElement(Text7, { style: styles7.cancelText }, "Cancel")
|
|
14810
15316
|
))
|
|
14811
15317
|
)
|
|
14812
15318
|
));
|
|
14813
15319
|
}
|
|
14814
|
-
var styles7 =
|
|
15320
|
+
var styles7 = StyleSheet9.create({
|
|
14815
15321
|
trigger: {
|
|
14816
15322
|
flexDirection: "row",
|
|
14817
15323
|
alignItems: "center",
|
|
@@ -14908,11 +15414,11 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14908
15414
|
const [affectedScreen, setAffectedScreen] = useState7("");
|
|
14909
15415
|
const [submitting, setSubmitting] = useState7(false);
|
|
14910
15416
|
const [error, setError] = useState7(null);
|
|
14911
|
-
const submittingRef =
|
|
15417
|
+
const submittingRef = useRef3(false);
|
|
14912
15418
|
const images = useImageAttachments(uploadImage, 5, "screenshots");
|
|
14913
15419
|
const isRetestFailure = prefill?.type === "test_fail";
|
|
14914
15420
|
const isBugType = reportType === "bug" || reportType === "test_fail";
|
|
14915
|
-
|
|
15421
|
+
useEffect6(() => {
|
|
14916
15422
|
if (reportType === "feedback" || reportType === "suggestion") {
|
|
14917
15423
|
setCategory("other");
|
|
14918
15424
|
} else {
|
|
@@ -14920,7 +15426,7 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14920
15426
|
}
|
|
14921
15427
|
}, [reportType]);
|
|
14922
15428
|
const handleSubmit = async () => {
|
|
14923
|
-
if (!client || !description.trim()) return;
|
|
15429
|
+
if (!client || !description.trim() || images.isUploading) return;
|
|
14924
15430
|
if (submittingRef.current) return;
|
|
14925
15431
|
submittingRef.current = true;
|
|
14926
15432
|
setSubmitting(true);
|
|
@@ -14961,21 +15467,21 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14961
15467
|
submittingRef.current = false;
|
|
14962
15468
|
}
|
|
14963
15469
|
};
|
|
14964
|
-
return /* @__PURE__ */
|
|
15470
|
+
return /* @__PURE__ */ React10.createElement(View9, null, isRetestFailure ? /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(View9, { style: styles8.retestBanner }, /* @__PURE__ */ React10.createElement(Text8, { style: styles8.retestIcon }, "\u{1F504}"), /* @__PURE__ */ React10.createElement(View9, null, /* @__PURE__ */ React10.createElement(Text8, { style: styles8.retestTitle }, "Bug Still Present"), /* @__PURE__ */ React10.createElement(Text8, { style: styles8.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Severity"), /* @__PURE__ */ React10.createElement(View9, { style: styles8.severityRow }, [
|
|
14965
15471
|
{ sev: "critical", color: "#ef4444" },
|
|
14966
15472
|
{ sev: "high", color: "#f97316" },
|
|
14967
15473
|
{ sev: "medium", color: "#eab308" },
|
|
14968
15474
|
{ sev: "low", color: "#6b7280" }
|
|
14969
|
-
].map(({ sev, color }) => /* @__PURE__ */
|
|
15475
|
+
].map(({ sev, color }) => /* @__PURE__ */ React10.createElement(
|
|
14970
15476
|
TouchableOpacity8,
|
|
14971
15477
|
{
|
|
14972
15478
|
key: sev,
|
|
14973
15479
|
style: [styles8.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
|
|
14974
15480
|
onPress: () => setSeverity(sev)
|
|
14975
15481
|
},
|
|
14976
|
-
/* @__PURE__ */
|
|
14977
|
-
)))), /* @__PURE__ */
|
|
14978
|
-
|
|
15482
|
+
/* @__PURE__ */ React10.createElement(Text8, { style: [styles8.sevText, severity === sev && { color }] }, sev)
|
|
15483
|
+
)))), /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Category (optional)"), /* @__PURE__ */ React10.createElement(CategoryPicker, { value: category, onChange: setCategory, optional: true })), /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "What went wrong?"), /* @__PURE__ */ React10.createElement(
|
|
15484
|
+
TextInput4,
|
|
14979
15485
|
{
|
|
14980
15486
|
style: styles8.descInput,
|
|
14981
15487
|
value: description,
|
|
@@ -14986,7 +15492,7 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14986
15492
|
numberOfLines: 4,
|
|
14987
15493
|
textAlignVertical: "top"
|
|
14988
15494
|
}
|
|
14989
|
-
)), /* @__PURE__ */
|
|
15495
|
+
)), /* @__PURE__ */ React10.createElement(
|
|
14990
15496
|
ImagePickerButtons,
|
|
14991
15497
|
{
|
|
14992
15498
|
images: images.images,
|
|
@@ -14996,42 +15502,42 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14996
15502
|
onRemove: images.removeImage,
|
|
14997
15503
|
label: "Attachments (optional)"
|
|
14998
15504
|
}
|
|
14999
|
-
), error && /* @__PURE__ */
|
|
15505
|
+
), error && /* @__PURE__ */ React10.createElement(View9, { style: styles8.errorBanner }, /* @__PURE__ */ React10.createElement(Text8, { style: styles8.errorText }, error)), /* @__PURE__ */ React10.createElement(
|
|
15000
15506
|
TouchableOpacity8,
|
|
15001
15507
|
{
|
|
15002
15508
|
style: [shared.primaryButton, styles8.retestSubmitButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
|
|
15003
15509
|
onPress: handleSubmit,
|
|
15004
15510
|
disabled: !description.trim() || submitting || images.isUploading
|
|
15005
15511
|
},
|
|
15006
|
-
/* @__PURE__ */
|
|
15007
|
-
)) : /* @__PURE__ */
|
|
15512
|
+
/* @__PURE__ */ React10.createElement(Text8, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Failed Retest")
|
|
15513
|
+
)) : /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "What are you reporting?"), /* @__PURE__ */ React10.createElement(View9, { style: styles8.typeRow }, [
|
|
15008
15514
|
{ type: "bug", label: "Bug", icon: "\u{1F41B}" },
|
|
15009
15515
|
{ type: "feedback", label: "Feedback", icon: "\u{1F4A1}" },
|
|
15010
15516
|
{ type: "suggestion", label: "Idea", icon: "\u2728" }
|
|
15011
|
-
].map(({ type, label, icon }) => /* @__PURE__ */
|
|
15517
|
+
].map(({ type, label, icon }) => /* @__PURE__ */ React10.createElement(
|
|
15012
15518
|
TouchableOpacity8,
|
|
15013
15519
|
{
|
|
15014
15520
|
key: type,
|
|
15015
15521
|
style: [styles8.typeCard, reportType === type && styles8.typeCardActive],
|
|
15016
15522
|
onPress: () => setReportType(type)
|
|
15017
15523
|
},
|
|
15018
|
-
/* @__PURE__ */
|
|
15019
|
-
/* @__PURE__ */
|
|
15020
|
-
))), isBugType && /* @__PURE__ */
|
|
15524
|
+
/* @__PURE__ */ React10.createElement(Text8, { style: styles8.typeIcon }, icon),
|
|
15525
|
+
/* @__PURE__ */ React10.createElement(Text8, { style: [styles8.typeLabel, reportType === type && styles8.typeLabelActive] }, label)
|
|
15526
|
+
))), isBugType && /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Severity"), /* @__PURE__ */ React10.createElement(View9, { style: styles8.severityRow }, [
|
|
15021
15527
|
{ sev: "critical", color: "#ef4444" },
|
|
15022
15528
|
{ sev: "high", color: "#f97316" },
|
|
15023
15529
|
{ sev: "medium", color: "#eab308" },
|
|
15024
15530
|
{ sev: "low", color: "#6b7280" }
|
|
15025
|
-
].map(({ sev, color }) => /* @__PURE__ */
|
|
15531
|
+
].map(({ sev, color }) => /* @__PURE__ */ React10.createElement(
|
|
15026
15532
|
TouchableOpacity8,
|
|
15027
15533
|
{
|
|
15028
15534
|
key: sev,
|
|
15029
15535
|
style: [styles8.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
|
|
15030
15536
|
onPress: () => setSeverity(sev)
|
|
15031
15537
|
},
|
|
15032
|
-
/* @__PURE__ */
|
|
15033
|
-
)))), isBugType && /* @__PURE__ */
|
|
15034
|
-
|
|
15538
|
+
/* @__PURE__ */ React10.createElement(Text8, { style: [styles8.sevText, severity === sev && { color }] }, sev)
|
|
15539
|
+
)))), isBugType && /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Category (optional)"), /* @__PURE__ */ React10.createElement(CategoryPicker, { value: category, onChange: setCategory, optional: true })), /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "What happened?"), /* @__PURE__ */ React10.createElement(
|
|
15540
|
+
TextInput4,
|
|
15035
15541
|
{
|
|
15036
15542
|
style: styles8.descInput,
|
|
15037
15543
|
value: description,
|
|
@@ -15042,8 +15548,8 @@ function ReportScreen({ nav, prefill }) {
|
|
|
15042
15548
|
numberOfLines: 4,
|
|
15043
15549
|
textAlignVertical: "top"
|
|
15044
15550
|
}
|
|
15045
|
-
)), isBugType && /* @__PURE__ */
|
|
15046
|
-
|
|
15551
|
+
)), isBugType && /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Which screen?"), /* @__PURE__ */ React10.createElement(
|
|
15552
|
+
TextInput4,
|
|
15047
15553
|
{
|
|
15048
15554
|
style: styles8.screenInput,
|
|
15049
15555
|
value: affectedScreen,
|
|
@@ -15051,7 +15557,7 @@ function ReportScreen({ nav, prefill }) {
|
|
|
15051
15557
|
placeholder: "e.g. Reservations, Settings...",
|
|
15052
15558
|
placeholderTextColor: colors.textMuted
|
|
15053
15559
|
}
|
|
15054
|
-
), /* @__PURE__ */
|
|
15560
|
+
), /* @__PURE__ */ React10.createElement(Text8, { style: styles8.screenHint }, "Which screen or area was the bug on? (optional)")), /* @__PURE__ */ React10.createElement(
|
|
15055
15561
|
ImagePickerButtons,
|
|
15056
15562
|
{
|
|
15057
15563
|
images: images.images,
|
|
@@ -15061,17 +15567,17 @@ function ReportScreen({ nav, prefill }) {
|
|
|
15061
15567
|
onRemove: images.removeImage,
|
|
15062
15568
|
label: "Screenshots (optional)"
|
|
15063
15569
|
}
|
|
15064
|
-
), error && /* @__PURE__ */
|
|
15570
|
+
), error && /* @__PURE__ */ React10.createElement(View9, { style: styles8.errorBanner }, /* @__PURE__ */ React10.createElement(Text8, { style: styles8.errorText }, error)), /* @__PURE__ */ React10.createElement(
|
|
15065
15571
|
TouchableOpacity8,
|
|
15066
15572
|
{
|
|
15067
15573
|
style: [shared.primaryButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
|
|
15068
15574
|
onPress: handleSubmit,
|
|
15069
15575
|
disabled: !description.trim() || submitting || images.isUploading
|
|
15070
15576
|
},
|
|
15071
|
-
/* @__PURE__ */
|
|
15577
|
+
/* @__PURE__ */ React10.createElement(Text8, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Report")
|
|
15072
15578
|
)));
|
|
15073
15579
|
}
|
|
15074
|
-
var styles8 =
|
|
15580
|
+
var styles8 = StyleSheet10.create({
|
|
15075
15581
|
typeRow: { flexDirection: "row", gap: 10, marginBottom: 20 },
|
|
15076
15582
|
typeCard: { flex: 1, alignItems: "center", paddingVertical: 16, borderRadius: 12, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
|
|
15077
15583
|
typeCardActive: { borderColor: colors.blue, backgroundColor: "#172554" },
|
|
@@ -15095,16 +15601,16 @@ var styles8 = StyleSheet9.create({
|
|
|
15095
15601
|
});
|
|
15096
15602
|
|
|
15097
15603
|
// src/widget/screens/ReportSuccessScreen.tsx
|
|
15098
|
-
import
|
|
15099
|
-
import { View as
|
|
15604
|
+
import React11, { useEffect as useEffect7 } from "react";
|
|
15605
|
+
import { View as View10, Text as Text9, StyleSheet as StyleSheet11 } from "react-native";
|
|
15100
15606
|
function ReportSuccessScreen({ nav }) {
|
|
15101
|
-
|
|
15607
|
+
useEffect7(() => {
|
|
15102
15608
|
const timer = setTimeout(() => nav.reset(), 2e3);
|
|
15103
15609
|
return () => clearTimeout(timer);
|
|
15104
15610
|
}, [nav]);
|
|
15105
|
-
return /* @__PURE__ */
|
|
15611
|
+
return /* @__PURE__ */ React11.createElement(View10, { style: styles9.container }, /* @__PURE__ */ React11.createElement(Text9, { style: styles9.emoji }, "\u{1F389}"), /* @__PURE__ */ React11.createElement(Text9, { style: styles9.title }, "Report submitted!"), /* @__PURE__ */ React11.createElement(Text9, { style: styles9.subtitle }, "Thank you for your feedback"));
|
|
15106
15612
|
}
|
|
15107
|
-
var styles9 =
|
|
15613
|
+
var styles9 = StyleSheet11.create({
|
|
15108
15614
|
container: { alignItems: "center", paddingVertical: 60 },
|
|
15109
15615
|
emoji: { fontSize: 48, marginBottom: 16 },
|
|
15110
15616
|
title: { fontSize: 22, fontWeight: "700", color: colors.textPrimary, marginBottom: 6 },
|
|
@@ -15112,29 +15618,30 @@ var styles9 = StyleSheet10.create({
|
|
|
15112
15618
|
});
|
|
15113
15619
|
|
|
15114
15620
|
// src/widget/screens/MessageListScreen.tsx
|
|
15115
|
-
import
|
|
15116
|
-
import { View as
|
|
15621
|
+
import React12 from "react";
|
|
15622
|
+
import { View as View11, Text as Text10, TouchableOpacity as TouchableOpacity9, StyleSheet as StyleSheet12 } from "react-native";
|
|
15117
15623
|
function MessageListScreen({ nav }) {
|
|
15118
|
-
const { threads, unreadCount, refreshThreads } = useBugBear();
|
|
15119
|
-
return /* @__PURE__ */
|
|
15624
|
+
const { threads, unreadCount, refreshThreads, isLoading } = useBugBear();
|
|
15625
|
+
if (isLoading) return /* @__PURE__ */ React12.createElement(MessageListScreenSkeleton, null);
|
|
15626
|
+
return /* @__PURE__ */ React12.createElement(View11, null, /* @__PURE__ */ React12.createElement(
|
|
15120
15627
|
TouchableOpacity9,
|
|
15121
15628
|
{
|
|
15122
15629
|
style: styles10.newMsgButton,
|
|
15123
15630
|
onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
|
|
15124
15631
|
},
|
|
15125
|
-
/* @__PURE__ */
|
|
15126
|
-
), threads.length === 0 ? /* @__PURE__ */
|
|
15632
|
+
/* @__PURE__ */ React12.createElement(Text10, { style: styles10.newMsgText }, "\u2709\uFE0F New Message")
|
|
15633
|
+
), threads.length === 0 ? /* @__PURE__ */ React12.createElement(View11, { style: shared.emptyState }, /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptyTitle }, "No messages yet"), /* @__PURE__ */ React12.createElement(Text10, { style: shared.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ React12.createElement(View11, null, threads.map((thread) => /* @__PURE__ */ React12.createElement(
|
|
15127
15634
|
TouchableOpacity9,
|
|
15128
15635
|
{
|
|
15129
15636
|
key: thread.id,
|
|
15130
15637
|
style: [styles10.threadItem, thread.unreadCount > 0 && styles10.threadItemUnread],
|
|
15131
15638
|
onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
|
|
15132
15639
|
},
|
|
15133
|
-
/* @__PURE__ */
|
|
15134
|
-
/* @__PURE__ */
|
|
15135
|
-
))), /* @__PURE__ */
|
|
15640
|
+
/* @__PURE__ */ React12.createElement(View11, { style: styles10.threadLeft }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.threadIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ React12.createElement(View11, { style: styles10.threadInfo }, /* @__PURE__ */ React12.createElement(View11, { style: styles10.threadTitleRow }, thread.isPinned && /* @__PURE__ */ React12.createElement(Text10, { style: styles10.pinIcon }, "\u{1F4CC}"), /* @__PURE__ */ React12.createElement(Text10, { style: styles10.threadSubject, numberOfLines: 1 }, thread.subject || "No subject")), thread.lastMessage && /* @__PURE__ */ React12.createElement(Text10, { style: styles10.threadPreview, numberOfLines: 1 }, thread.lastMessage.senderName, ": ", thread.lastMessage.content))),
|
|
15641
|
+
/* @__PURE__ */ React12.createElement(View11, { style: styles10.threadRight }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.threadTime }, formatRelativeTime(thread.lastMessageAt)), thread.unreadCount > 0 && /* @__PURE__ */ React12.createElement(View11, { style: styles10.unreadBadge }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.unreadText }, thread.unreadCount)), thread.priority !== "normal" && /* @__PURE__ */ React12.createElement(View11, { style: [styles10.priorityDot, { backgroundColor: getPriorityColor(thread.priority) }] }))
|
|
15642
|
+
))), /* @__PURE__ */ React12.createElement(View11, { style: styles10.footer }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.footerText }, threads.length, " thread", threads.length !== 1 ? "s" : "", " \xB7 ", unreadCount, " unread"), /* @__PURE__ */ React12.createElement(TouchableOpacity9, { onPress: refreshThreads }, /* @__PURE__ */ React12.createElement(Text10, { style: styles10.refreshText }, "\u21BB Refresh"))));
|
|
15136
15643
|
}
|
|
15137
|
-
var styles10 =
|
|
15644
|
+
var styles10 = StyleSheet12.create({
|
|
15138
15645
|
newMsgButton: { backgroundColor: colors.blue, paddingVertical: 12, borderRadius: 12, alignItems: "center", marginBottom: 16 },
|
|
15139
15646
|
newMsgText: { fontSize: 15, fontWeight: "600", color: "#fff" },
|
|
15140
15647
|
threadItem: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 12, paddingHorizontal: 12, borderRadius: 10, marginBottom: 4, backgroundColor: colors.card },
|
|
@@ -15157,8 +15664,8 @@ var styles10 = StyleSheet11.create({
|
|
|
15157
15664
|
});
|
|
15158
15665
|
|
|
15159
15666
|
// src/widget/screens/ThreadDetailScreen.tsx
|
|
15160
|
-
import
|
|
15161
|
-
import { View as
|
|
15667
|
+
import React13, { useState as useState8, useEffect as useEffect8 } from "react";
|
|
15668
|
+
import { View as View12, Text as Text11, TouchableOpacity as TouchableOpacity10, TextInput as TextInput5, StyleSheet as StyleSheet13, Image as Image2 } from "react-native";
|
|
15162
15669
|
function ThreadDetailScreen({ thread, nav }) {
|
|
15163
15670
|
const { getThreadMessages, sendMessage, markAsRead, uploadImage } = useBugBear();
|
|
15164
15671
|
const [messages, setMessages] = useState8([]);
|
|
@@ -15167,16 +15674,29 @@ function ThreadDetailScreen({ thread, nav }) {
|
|
|
15167
15674
|
const [sending, setSending] = useState8(false);
|
|
15168
15675
|
const [sendError, setSendError] = useState8(false);
|
|
15169
15676
|
const replyImages = useImageAttachments(uploadImage, 3, "discussion-attachments");
|
|
15170
|
-
|
|
15677
|
+
useEffect8(() => {
|
|
15678
|
+
let cancelled = false;
|
|
15679
|
+
setLoading(true);
|
|
15171
15680
|
(async () => {
|
|
15172
|
-
|
|
15173
|
-
|
|
15174
|
-
|
|
15175
|
-
|
|
15176
|
-
|
|
15177
|
-
|
|
15681
|
+
try {
|
|
15682
|
+
const msgs = await getThreadMessages(thread.id);
|
|
15683
|
+
if (!cancelled) {
|
|
15684
|
+
setMessages(msgs);
|
|
15685
|
+
}
|
|
15686
|
+
if (thread.unreadCount > 0) {
|
|
15687
|
+
await markAsRead(thread.id);
|
|
15688
|
+
}
|
|
15689
|
+
} catch (err) {
|
|
15690
|
+
console.error("BugBear: Failed to load thread messages", err);
|
|
15691
|
+
} finally {
|
|
15692
|
+
if (!cancelled) {
|
|
15693
|
+
setLoading(false);
|
|
15694
|
+
}
|
|
15178
15695
|
}
|
|
15179
15696
|
})();
|
|
15697
|
+
return () => {
|
|
15698
|
+
cancelled = true;
|
|
15699
|
+
};
|
|
15180
15700
|
}, [thread.id]);
|
|
15181
15701
|
const handleSend = async () => {
|
|
15182
15702
|
if (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) return;
|
|
@@ -15199,18 +15719,18 @@ function ThreadDetailScreen({ thread, nav }) {
|
|
|
15199
15719
|
}
|
|
15200
15720
|
setSending(false);
|
|
15201
15721
|
};
|
|
15202
|
-
return /* @__PURE__ */
|
|
15203
|
-
|
|
15722
|
+
return /* @__PURE__ */ React13.createElement(View12, { style: styles11.container }, /* @__PURE__ */ React13.createElement(View12, { style: styles11.header }, /* @__PURE__ */ React13.createElement(Text11, { style: styles11.headerIcon }, getThreadTypeIcon(thread.threadType)), /* @__PURE__ */ React13.createElement(Text11, { style: styles11.headerSubject, numberOfLines: 2 }, thread.subject || "No subject")), loading ? /* @__PURE__ */ React13.createElement(View12, { style: styles11.loadingContainer }, /* @__PURE__ */ React13.createElement(Text11, { style: styles11.loadingText }, "Loading messages...")) : /* @__PURE__ */ React13.createElement(View12, { style: styles11.messagesContainer }, messages.map((msg) => /* @__PURE__ */ React13.createElement(
|
|
15723
|
+
View12,
|
|
15204
15724
|
{
|
|
15205
15725
|
key: msg.id,
|
|
15206
15726
|
style: [styles11.bubble, msg.senderType === "tester" ? styles11.bubbleTester : styles11.bubbleAdmin]
|
|
15207
15727
|
},
|
|
15208
|
-
/* @__PURE__ */
|
|
15209
|
-
/* @__PURE__ */
|
|
15210
|
-
msg.attachments && msg.attachments.length > 0 && /* @__PURE__ */
|
|
15211
|
-
/* @__PURE__ */
|
|
15212
|
-
))), sendError && /* @__PURE__ */
|
|
15213
|
-
|
|
15728
|
+
/* @__PURE__ */ React13.createElement(Text11, { style: [styles11.sender, msg.senderType === "tester" && styles11.senderTester] }, msg.senderType === "tester" ? "You" : msg.senderName),
|
|
15729
|
+
/* @__PURE__ */ React13.createElement(Text11, { style: [styles11.content, msg.senderType === "tester" && styles11.contentTester] }, msg.content),
|
|
15730
|
+
msg.attachments && msg.attachments.length > 0 && /* @__PURE__ */ React13.createElement(View12, { style: styles11.attachments }, msg.attachments.filter((a) => a.type === "image").map((att, idx) => /* @__PURE__ */ React13.createElement(Image2, { key: idx, source: { uri: att.url }, style: styles11.attachmentImage, resizeMode: "cover" }))),
|
|
15731
|
+
/* @__PURE__ */ React13.createElement(Text11, { style: [styles11.time, msg.senderType === "tester" && styles11.timeTester] }, formatMessageTime(msg.createdAt))
|
|
15732
|
+
))), sendError && /* @__PURE__ */ React13.createElement(View12, { style: styles11.errorBar }, /* @__PURE__ */ React13.createElement(Text11, { style: styles11.errorText }, "Failed to send. Tap Send to retry.")), replyImages.images.length > 0 && /* @__PURE__ */ React13.createElement(View12, { style: styles11.replyPreview }, /* @__PURE__ */ React13.createElement(ImagePreviewStrip, { images: replyImages.images, onRemove: replyImages.removeImage })), /* @__PURE__ */ React13.createElement(View12, { style: styles11.composer }, IMAGE_PICKER_AVAILABLE && /* @__PURE__ */ React13.createElement(TouchableOpacity10, { style: styles11.attachBtn, onPress: replyImages.pickFromGallery, disabled: replyImages.images.length >= 3 }, /* @__PURE__ */ React13.createElement(Text11, { style: styles11.attachBtnText }, "\u{1F4CE}")), /* @__PURE__ */ React13.createElement(
|
|
15733
|
+
TextInput5,
|
|
15214
15734
|
{
|
|
15215
15735
|
style: styles11.replyInput,
|
|
15216
15736
|
value: replyText,
|
|
@@ -15220,17 +15740,17 @@ function ThreadDetailScreen({ thread, nav }) {
|
|
|
15220
15740
|
multiline: true,
|
|
15221
15741
|
maxLength: 1e3
|
|
15222
15742
|
}
|
|
15223
|
-
), /* @__PURE__ */
|
|
15743
|
+
), /* @__PURE__ */ React13.createElement(
|
|
15224
15744
|
TouchableOpacity10,
|
|
15225
15745
|
{
|
|
15226
|
-
style: [styles11.sendBtn, (!replyText.trim() || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
|
|
15746
|
+
style: [styles11.sendBtn, (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
|
|
15227
15747
|
onPress: handleSend,
|
|
15228
|
-
disabled: !replyText.trim() || sending || replyImages.isUploading
|
|
15748
|
+
disabled: !replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading
|
|
15229
15749
|
},
|
|
15230
|
-
/* @__PURE__ */
|
|
15750
|
+
/* @__PURE__ */ React13.createElement(Text11, { style: styles11.sendBtnText }, sending ? "..." : "Send")
|
|
15231
15751
|
)));
|
|
15232
15752
|
}
|
|
15233
|
-
var styles11 =
|
|
15753
|
+
var styles11 = StyleSheet13.create({
|
|
15234
15754
|
container: { flex: 1 },
|
|
15235
15755
|
header: { flexDirection: "row", alignItems: "center", gap: 8, marginBottom: 16, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: colors.border },
|
|
15236
15756
|
headerIcon: { fontSize: 20 },
|
|
@@ -15262,8 +15782,8 @@ var styles11 = StyleSheet12.create({
|
|
|
15262
15782
|
});
|
|
15263
15783
|
|
|
15264
15784
|
// src/widget/screens/ComposeMessageScreen.tsx
|
|
15265
|
-
import
|
|
15266
|
-
import { View as
|
|
15785
|
+
import React14, { useState as useState9 } from "react";
|
|
15786
|
+
import { View as View13, Text as Text12, TextInput as TextInput6, TouchableOpacity as TouchableOpacity11, StyleSheet as StyleSheet14 } from "react-native";
|
|
15267
15787
|
function ComposeMessageScreen({ nav }) {
|
|
15268
15788
|
const { createThread, uploadImage } = useBugBear();
|
|
15269
15789
|
const [subject, setSubject] = useState9("");
|
|
@@ -15271,7 +15791,7 @@ function ComposeMessageScreen({ nav }) {
|
|
|
15271
15791
|
const [sending, setSending] = useState9(false);
|
|
15272
15792
|
const images = useImageAttachments(uploadImage, 3, "discussion-attachments");
|
|
15273
15793
|
const handleSend = async () => {
|
|
15274
|
-
if (!subject.trim() || !message.trim()) return;
|
|
15794
|
+
if (!subject.trim() || !message.trim() || sending || images.isUploading) return;
|
|
15275
15795
|
setSending(true);
|
|
15276
15796
|
const attachments = images.getAttachments();
|
|
15277
15797
|
const result = await createThread({
|
|
@@ -15284,8 +15804,8 @@ function ComposeMessageScreen({ nav }) {
|
|
|
15284
15804
|
nav.pop();
|
|
15285
15805
|
}
|
|
15286
15806
|
};
|
|
15287
|
-
return /* @__PURE__ */
|
|
15288
|
-
|
|
15807
|
+
return /* @__PURE__ */ React14.createElement(View13, null, /* @__PURE__ */ React14.createElement(View13, { style: styles12.header }, /* @__PURE__ */ React14.createElement(Text12, { style: styles12.title }, "New Message"), /* @__PURE__ */ React14.createElement(Text12, { style: styles12.subtitle }, "Send a message to the QA team")), /* @__PURE__ */ React14.createElement(View13, { style: styles12.form }, /* @__PURE__ */ React14.createElement(Text12, { style: shared.label }, "Subject"), /* @__PURE__ */ React14.createElement(
|
|
15808
|
+
TextInput6,
|
|
15289
15809
|
{
|
|
15290
15810
|
style: styles12.subjectInput,
|
|
15291
15811
|
value: subject,
|
|
@@ -15294,8 +15814,8 @@ function ComposeMessageScreen({ nav }) {
|
|
|
15294
15814
|
placeholderTextColor: colors.textMuted,
|
|
15295
15815
|
maxLength: 100
|
|
15296
15816
|
}
|
|
15297
|
-
), /* @__PURE__ */
|
|
15298
|
-
|
|
15817
|
+
), /* @__PURE__ */ React14.createElement(Text12, { style: [shared.label, { marginTop: 16 }] }, "Message"), /* @__PURE__ */ React14.createElement(
|
|
15818
|
+
TextInput6,
|
|
15299
15819
|
{
|
|
15300
15820
|
style: styles12.messageInput,
|
|
15301
15821
|
value: message,
|
|
@@ -15307,7 +15827,7 @@ function ComposeMessageScreen({ nav }) {
|
|
|
15307
15827
|
textAlignVertical: "top",
|
|
15308
15828
|
maxLength: 2e3
|
|
15309
15829
|
}
|
|
15310
|
-
), /* @__PURE__ */
|
|
15830
|
+
), /* @__PURE__ */ React14.createElement(
|
|
15311
15831
|
ImagePickerButtons,
|
|
15312
15832
|
{
|
|
15313
15833
|
images: images.images,
|
|
@@ -15316,17 +15836,17 @@ function ComposeMessageScreen({ nav }) {
|
|
|
15316
15836
|
onPickCamera: images.pickFromCamera,
|
|
15317
15837
|
onRemove: images.removeImage
|
|
15318
15838
|
}
|
|
15319
|
-
), /* @__PURE__ */
|
|
15839
|
+
), /* @__PURE__ */ React14.createElement(
|
|
15320
15840
|
TouchableOpacity11,
|
|
15321
15841
|
{
|
|
15322
15842
|
style: [shared.primaryButton, (!subject.trim() || !message.trim() || sending || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
|
|
15323
15843
|
onPress: handleSend,
|
|
15324
15844
|
disabled: !subject.trim() || !message.trim() || sending || images.isUploading
|
|
15325
15845
|
},
|
|
15326
|
-
/* @__PURE__ */
|
|
15846
|
+
/* @__PURE__ */ React14.createElement(Text12, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : sending ? "Sending..." : "Send Message")
|
|
15327
15847
|
)));
|
|
15328
15848
|
}
|
|
15329
|
-
var styles12 =
|
|
15849
|
+
var styles12 = StyleSheet14.create({
|
|
15330
15850
|
header: { marginBottom: 20 },
|
|
15331
15851
|
title: { fontSize: 20, fontWeight: "600", color: colors.textPrimary, marginBottom: 4 },
|
|
15332
15852
|
subtitle: { fontSize: 14, color: colors.textMuted },
|
|
@@ -15336,8 +15856,8 @@ var styles12 = StyleSheet13.create({
|
|
|
15336
15856
|
});
|
|
15337
15857
|
|
|
15338
15858
|
// src/widget/screens/ProfileScreen.tsx
|
|
15339
|
-
import
|
|
15340
|
-
import { View as
|
|
15859
|
+
import React15, { useState as useState10, useEffect as useEffect9 } from "react";
|
|
15860
|
+
import { View as View14, Text as Text13, TouchableOpacity as TouchableOpacity12, TextInput as TextInput7, StyleSheet as StyleSheet15 } from "react-native";
|
|
15341
15861
|
function ProfileScreen({ nav }) {
|
|
15342
15862
|
const { testerInfo, assignments, updateTesterProfile, refreshTesterInfo } = useBugBear();
|
|
15343
15863
|
const [editing, setEditing] = useState10(false);
|
|
@@ -15349,7 +15869,7 @@ function ProfileScreen({ nav }) {
|
|
|
15349
15869
|
const [saved, setSaved] = useState10(false);
|
|
15350
15870
|
const [showDetails, setShowDetails] = useState10(false);
|
|
15351
15871
|
const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
|
|
15352
|
-
|
|
15872
|
+
useEffect9(() => {
|
|
15353
15873
|
if (testerInfo) {
|
|
15354
15874
|
setName(testerInfo.name);
|
|
15355
15875
|
setAdditionalEmails(testerInfo.additionalEmails || []);
|
|
@@ -15357,6 +15877,7 @@ function ProfileScreen({ nav }) {
|
|
|
15357
15877
|
}
|
|
15358
15878
|
}, [testerInfo]);
|
|
15359
15879
|
const handleSave = async () => {
|
|
15880
|
+
if (saving) return;
|
|
15360
15881
|
setSaving(true);
|
|
15361
15882
|
const updates = {
|
|
15362
15883
|
name: name.trim(),
|
|
@@ -15383,17 +15904,17 @@ function ProfileScreen({ nav }) {
|
|
|
15383
15904
|
}
|
|
15384
15905
|
};
|
|
15385
15906
|
if (saved) {
|
|
15386
|
-
return /* @__PURE__ */
|
|
15907
|
+
return /* @__PURE__ */ React15.createElement(View14, { style: shared.emptyState }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.emptyEmoji }, "\u2705"), /* @__PURE__ */ React15.createElement(Text13, { style: shared.emptyTitle }, "Profile saved!"));
|
|
15387
15908
|
}
|
|
15388
15909
|
if (!testerInfo) {
|
|
15389
|
-
return /* @__PURE__ */
|
|
15910
|
+
return /* @__PURE__ */ React15.createElement(View14, { style: shared.emptyState }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.emptyEmoji }, "\u{1F464}"), /* @__PURE__ */ React15.createElement(Text13, { style: shared.emptyTitle }, "No profile found"));
|
|
15390
15911
|
}
|
|
15391
15912
|
if (editing) {
|
|
15392
|
-
return /* @__PURE__ */
|
|
15913
|
+
return /* @__PURE__ */ React15.createElement(View14, null, /* @__PURE__ */ React15.createElement(View14, { style: styles13.editHeader }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.editTitle }, "Edit Profile"), /* @__PURE__ */ React15.createElement(TouchableOpacity12, { onPress: () => {
|
|
15393
15914
|
setEditing(false);
|
|
15394
15915
|
setNewEmailInput("");
|
|
15395
|
-
} }, /* @__PURE__ */
|
|
15396
|
-
|
|
15916
|
+
} }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.cancelText }, "Cancel"))), /* @__PURE__ */ React15.createElement(View14, { style: styles13.field }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.label }, "Name"), /* @__PURE__ */ React15.createElement(TextInput7, { style: styles13.input, value: name, onChangeText: setName, placeholder: "Your name", placeholderTextColor: colors.textMuted })), /* @__PURE__ */ React15.createElement(View14, { style: styles13.field }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.label }, "Primary Email"), /* @__PURE__ */ React15.createElement(Text13, { style: styles13.emailFixed }, testerInfo.email)), /* @__PURE__ */ React15.createElement(View14, { style: styles13.field }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.label }, "Additional Emails"), additionalEmails.map((email) => /* @__PURE__ */ React15.createElement(View14, { key: email, style: styles13.emailRow }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.emailText }, email), /* @__PURE__ */ React15.createElement(TouchableOpacity12, { onPress: () => setAdditionalEmails(additionalEmails.filter((e) => e !== email)) }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.removeEmail }, "\u2715")))), /* @__PURE__ */ React15.createElement(View14, { style: styles13.addEmailRow }, /* @__PURE__ */ React15.createElement(
|
|
15917
|
+
TextInput7,
|
|
15397
15918
|
{
|
|
15398
15919
|
style: [styles13.input, { flex: 1, marginRight: 8 }],
|
|
15399
15920
|
value: newEmailInput,
|
|
@@ -15403,26 +15924,26 @@ function ProfileScreen({ nav }) {
|
|
|
15403
15924
|
keyboardType: "email-address",
|
|
15404
15925
|
autoCapitalize: "none"
|
|
15405
15926
|
}
|
|
15406
|
-
), /* @__PURE__ */
|
|
15927
|
+
), /* @__PURE__ */ React15.createElement(TouchableOpacity12, { style: styles13.addButton, onPress: handleAddEmail }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.addButtonText }, "Add")))), /* @__PURE__ */ React15.createElement(View14, { style: styles13.field }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.label }, "Testing Platforms"), /* @__PURE__ */ React15.createElement(View14, { style: styles13.platformRow }, [{ key: "ios", label: "\u{1F4F1} iOS" }, { key: "android", label: "\u{1F916} Android" }, { key: "web", label: "\u{1F310} Web" }].map(({ key, label }) => /* @__PURE__ */ React15.createElement(
|
|
15407
15928
|
TouchableOpacity12,
|
|
15408
15929
|
{
|
|
15409
15930
|
key,
|
|
15410
15931
|
style: [styles13.platformBtn, platforms.includes(key) && styles13.platformBtnActive],
|
|
15411
15932
|
onPress: () => setPlatforms((prev) => prev.includes(key) ? prev.filter((p) => p !== key) : [...prev, key])
|
|
15412
15933
|
},
|
|
15413
|
-
/* @__PURE__ */
|
|
15414
|
-
)))), /* @__PURE__ */
|
|
15934
|
+
/* @__PURE__ */ React15.createElement(Text13, { style: [styles13.platformText, platforms.includes(key) && styles13.platformTextActive] }, label)
|
|
15935
|
+
)))), /* @__PURE__ */ React15.createElement(TouchableOpacity12, { style: [shared.primaryButton, { marginTop: 20 }], onPress: handleSave, disabled: saving }, /* @__PURE__ */ React15.createElement(Text13, { style: shared.primaryButtonText }, saving ? "Saving..." : "Save Profile")));
|
|
15415
15936
|
}
|
|
15416
|
-
return /* @__PURE__ */
|
|
15937
|
+
return /* @__PURE__ */ React15.createElement(View14, null, /* @__PURE__ */ React15.createElement(View14, { style: styles13.profileCard }, /* @__PURE__ */ React15.createElement(View14, { style: styles13.avatar }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.avatarText }, testerInfo.name.charAt(0).toUpperCase())), /* @__PURE__ */ React15.createElement(Text13, { style: styles13.profileName }, testerInfo.name), /* @__PURE__ */ React15.createElement(Text13, { style: styles13.profileEmail }, testerInfo.email)), /* @__PURE__ */ React15.createElement(View14, { style: styles13.statsRow }, /* @__PURE__ */ React15.createElement(View14, { style: styles13.statItem }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.statNumber }, completedCount), /* @__PURE__ */ React15.createElement(Text13, { style: styles13.statLabel }, "Completed")), /* @__PURE__ */ React15.createElement(View14, { style: styles13.statDivider }), /* @__PURE__ */ React15.createElement(View14, { style: styles13.statItem }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.statNumber }, assignments.length), /* @__PURE__ */ React15.createElement(Text13, { style: styles13.statLabel }, "Total Assigned"))), /* @__PURE__ */ React15.createElement(TouchableOpacity12, { onPress: () => setShowDetails(!showDetails), style: styles13.detailsToggle }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.detailsToggleText }, showDetails ? "\u25BC" : "\u25B6", " Details")), showDetails && /* @__PURE__ */ React15.createElement(View14, { style: styles13.detailsSection }, additionalEmails.length > 0 && /* @__PURE__ */ React15.createElement(View14, { style: styles13.detailBlock }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.detailLabel }, "Additional Emails"), additionalEmails.map((e) => /* @__PURE__ */ React15.createElement(Text13, { key: e, style: styles13.detailValue }, e))), platforms.length > 0 && /* @__PURE__ */ React15.createElement(View14, { style: styles13.detailBlock }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.detailLabel }, "Platforms"), /* @__PURE__ */ React15.createElement(View14, { style: styles13.platformTags }, platforms.map((p) => /* @__PURE__ */ React15.createElement(View14, { key: p, style: styles13.platformTag }, /* @__PURE__ */ React15.createElement(Text13, { style: styles13.platformTagText }, p === "ios" ? "\u{1F4F1} iOS" : p === "android" ? "\u{1F916} Android" : "\u{1F310} Web")))))), /* @__PURE__ */ React15.createElement(
|
|
15417
15938
|
TouchableOpacity12,
|
|
15418
15939
|
{
|
|
15419
15940
|
style: [shared.primaryButton, { marginTop: 20 }],
|
|
15420
15941
|
onPress: () => setEditing(true)
|
|
15421
15942
|
},
|
|
15422
|
-
/* @__PURE__ */
|
|
15943
|
+
/* @__PURE__ */ React15.createElement(Text13, { style: shared.primaryButtonText }, "Edit Profile")
|
|
15423
15944
|
));
|
|
15424
15945
|
}
|
|
15425
|
-
var styles13 =
|
|
15946
|
+
var styles13 = StyleSheet15.create({
|
|
15426
15947
|
profileCard: { alignItems: "center", backgroundColor: colors.card, borderRadius: 16, padding: 24, marginBottom: 16 },
|
|
15427
15948
|
avatar: { width: 64, height: 64, borderRadius: 32, backgroundColor: colors.blue, justifyContent: "center", alignItems: "center", marginBottom: 12 },
|
|
15428
15949
|
avatarText: { fontSize: 28, fontWeight: "700", color: "#fff" },
|
|
@@ -15463,8 +15984,8 @@ var styles13 = StyleSheet14.create({
|
|
|
15463
15984
|
});
|
|
15464
15985
|
|
|
15465
15986
|
// src/widget/screens/IssueListScreen.tsx
|
|
15466
|
-
import
|
|
15467
|
-
import { View as
|
|
15987
|
+
import React16, { useState as useState11, useEffect as useEffect10 } from "react";
|
|
15988
|
+
import { View as View15, Text as Text14, TouchableOpacity as TouchableOpacity13, StyleSheet as StyleSheet16 } from "react-native";
|
|
15468
15989
|
var CATEGORY_CONFIG = {
|
|
15469
15990
|
open: { label: "Open Issues", accent: "#f97316", emptyIcon: "\u2705", emptyText: "No open issues" },
|
|
15470
15991
|
done: { label: "Done", accent: "#22c55e", emptyIcon: "\u{1F389}", emptyText: "No completed issues yet" },
|
|
@@ -15481,15 +16002,25 @@ function IssueListScreen({ nav, category }) {
|
|
|
15481
16002
|
const [issues, setIssues] = useState11([]);
|
|
15482
16003
|
const [loading, setLoading] = useState11(true);
|
|
15483
16004
|
const config = CATEGORY_CONFIG[category];
|
|
15484
|
-
|
|
16005
|
+
useEffect10(() => {
|
|
15485
16006
|
let cancelled = false;
|
|
15486
16007
|
setLoading(true);
|
|
15487
16008
|
(async () => {
|
|
15488
|
-
if (!client)
|
|
15489
|
-
const data = await client.getIssues(category);
|
|
15490
|
-
if (!cancelled) {
|
|
15491
|
-
setIssues(data);
|
|
16009
|
+
if (!client) {
|
|
15492
16010
|
setLoading(false);
|
|
16011
|
+
return;
|
|
16012
|
+
}
|
|
16013
|
+
try {
|
|
16014
|
+
const data = await client.getIssues(category);
|
|
16015
|
+
if (!cancelled) {
|
|
16016
|
+
setIssues(data);
|
|
16017
|
+
}
|
|
16018
|
+
} catch (err) {
|
|
16019
|
+
console.error("BugBear: Failed to load issues", err);
|
|
16020
|
+
} finally {
|
|
16021
|
+
if (!cancelled) {
|
|
16022
|
+
setLoading(false);
|
|
16023
|
+
}
|
|
15493
16024
|
}
|
|
15494
16025
|
})();
|
|
15495
16026
|
return () => {
|
|
@@ -15497,12 +16028,12 @@ function IssueListScreen({ nav, category }) {
|
|
|
15497
16028
|
};
|
|
15498
16029
|
}, [client, category]);
|
|
15499
16030
|
if (loading) {
|
|
15500
|
-
return /* @__PURE__ */
|
|
16031
|
+
return /* @__PURE__ */ React16.createElement(IssueListScreenSkeleton, null);
|
|
15501
16032
|
}
|
|
15502
16033
|
if (issues.length === 0) {
|
|
15503
|
-
return /* @__PURE__ */
|
|
16034
|
+
return /* @__PURE__ */ React16.createElement(View15, { style: styles14.emptyContainer }, /* @__PURE__ */ React16.createElement(Text14, { style: styles14.emptyIcon }, config.emptyIcon), /* @__PURE__ */ React16.createElement(Text14, { style: styles14.emptyText }, config.emptyText));
|
|
15504
16035
|
}
|
|
15505
|
-
return /* @__PURE__ */
|
|
16036
|
+
return /* @__PURE__ */ React16.createElement(View15, null, issues.map((issue) => /* @__PURE__ */ React16.createElement(
|
|
15506
16037
|
TouchableOpacity13,
|
|
15507
16038
|
{
|
|
15508
16039
|
key: issue.id,
|
|
@@ -15510,13 +16041,13 @@ function IssueListScreen({ nav, category }) {
|
|
|
15510
16041
|
onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
|
|
15511
16042
|
activeOpacity: 0.7
|
|
15512
16043
|
},
|
|
15513
|
-
/* @__PURE__ */
|
|
15514
|
-
/* @__PURE__ */
|
|
15515
|
-
category === "done" && issue.verifiedByName && /* @__PURE__ */
|
|
15516
|
-
category === "reopened" && issue.originalBugTitle && /* @__PURE__ */
|
|
16044
|
+
/* @__PURE__ */ React16.createElement(View15, { style: styles14.topRow }, issue.severity && /* @__PURE__ */ React16.createElement(View15, { style: [styles14.severityDot, { backgroundColor: SEVERITY_COLORS[issue.severity] || colors.textDim }] }), /* @__PURE__ */ React16.createElement(Text14, { style: styles14.issueTitle, numberOfLines: 1 }, issue.title)),
|
|
16045
|
+
/* @__PURE__ */ React16.createElement(View15, { style: styles14.bottomRow }, issue.route && /* @__PURE__ */ React16.createElement(Text14, { style: styles14.routeText, numberOfLines: 1 }, issue.route), /* @__PURE__ */ React16.createElement(Text14, { style: styles14.timeText }, formatRelativeTime(issue.updatedAt))),
|
|
16046
|
+
category === "done" && issue.verifiedByName && /* @__PURE__ */ React16.createElement(View15, { style: styles14.verifiedBadge }, /* @__PURE__ */ React16.createElement(Text14, { style: styles14.verifiedBadgeText }, "\u2714", " Verified by ", issue.verifiedByName)),
|
|
16047
|
+
category === "reopened" && issue.originalBugTitle && /* @__PURE__ */ React16.createElement(View15, { style: styles14.reopenedBadge }, /* @__PURE__ */ React16.createElement(Text14, { style: styles14.reopenedBadgeText, numberOfLines: 1 }, "\u{1F504}", " Retest of: ", issue.originalBugTitle))
|
|
15517
16048
|
)));
|
|
15518
16049
|
}
|
|
15519
|
-
var styles14 =
|
|
16050
|
+
var styles14 = StyleSheet16.create({
|
|
15520
16051
|
emptyContainer: {
|
|
15521
16052
|
alignItems: "center",
|
|
15522
16053
|
paddingVertical: 40
|
|
@@ -15607,8 +16138,8 @@ var styles14 = StyleSheet15.create({
|
|
|
15607
16138
|
});
|
|
15608
16139
|
|
|
15609
16140
|
// src/widget/screens/IssueDetailScreen.tsx
|
|
15610
|
-
import
|
|
15611
|
-
import { View as
|
|
16141
|
+
import React17 from "react";
|
|
16142
|
+
import { View as View16, Text as Text15, Image as Image3, StyleSheet as StyleSheet17, Linking as Linking2, TouchableOpacity as TouchableOpacity14 } from "react-native";
|
|
15612
16143
|
var STATUS_LABELS = {
|
|
15613
16144
|
new: { label: "New", bg: "#1e3a5f", color: "#60a5fa" },
|
|
15614
16145
|
triaging: { label: "Triaging", bg: "#1e3a5f", color: "#60a5fa" },
|
|
@@ -15632,9 +16163,9 @@ var SEVERITY_CONFIG = {
|
|
|
15632
16163
|
function IssueDetailScreen({ nav, issue }) {
|
|
15633
16164
|
const statusConfig = STATUS_LABELS[issue.status] || { label: issue.status, bg: "#27272a", color: "#a1a1aa" };
|
|
15634
16165
|
const severityConfig = issue.severity ? SEVERITY_CONFIG[issue.severity] : null;
|
|
15635
|
-
return /* @__PURE__ */
|
|
16166
|
+
return /* @__PURE__ */ React17.createElement(View16, null, /* @__PURE__ */ React17.createElement(View16, { style: styles15.badgeRow }, /* @__PURE__ */ React17.createElement(View16, { style: [styles15.badge, { backgroundColor: statusConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles15.badgeText, { color: statusConfig.color }] }, statusConfig.label)), severityConfig && /* @__PURE__ */ React17.createElement(View16, { style: [styles15.badge, { backgroundColor: severityConfig.bg }] }, /* @__PURE__ */ React17.createElement(Text15, { style: [styles15.badgeText, { color: severityConfig.color }] }, severityConfig.label))), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.title }, issue.title), issue.route && /* @__PURE__ */ React17.createElement(Text15, { style: styles15.route }, issue.route), issue.description && /* @__PURE__ */ React17.createElement(View16, { style: styles15.descriptionCard }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.descriptionText }, issue.description)), issue.verifiedByName && /* @__PURE__ */ React17.createElement(View16, { style: styles15.verifiedCard }, /* @__PURE__ */ React17.createElement(View16, { style: styles15.verifiedHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.verifiedIcon }, "\u2705"), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.verifiedTitle }, "Retesting Proof")), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.verifiedBody }, "Verified by ", issue.verifiedByName, issue.verifiedAt && ` on ${new Date(issue.verifiedAt).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" })}`)), issue.originalBugTitle && /* @__PURE__ */ React17.createElement(View16, { style: styles15.originalBugCard }, /* @__PURE__ */ React17.createElement(View16, { style: styles15.originalBugHeader }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.originalBugIcon }, "\u{1F504}"), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.originalBugTitle }, "Original Bug")), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.originalBugBody }, "Retest of: ", issue.originalBugTitle)), issue.screenshotUrls && issue.screenshotUrls.length > 0 && /* @__PURE__ */ React17.createElement(View16, { style: styles15.screenshotSection }, /* @__PURE__ */ React17.createElement(Text15, { style: styles15.screenshotLabel }, "Screenshots (", issue.screenshotUrls.length, ")"), /* @__PURE__ */ React17.createElement(View16, { style: styles15.screenshotRow }, issue.screenshotUrls.map((url, i) => /* @__PURE__ */ React17.createElement(TouchableOpacity14, { key: i, onPress: () => Linking2.openURL(url), activeOpacity: 0.7 }, /* @__PURE__ */ React17.createElement(Image3, { source: { uri: url }, style: styles15.screenshotThumb }))))), /* @__PURE__ */ React17.createElement(View16, { style: styles15.metaSection }, issue.reporterName && /* @__PURE__ */ React17.createElement(Text15, { style: styles15.metaText }, "Reported by ", issue.reporterName), /* @__PURE__ */ React17.createElement(Text15, { style: styles15.metaTextSmall }, "Created ", formatRelativeTime(issue.createdAt), " ", "\xB7", " Updated ", formatRelativeTime(issue.updatedAt))));
|
|
15636
16167
|
}
|
|
15637
|
-
var styles15 =
|
|
16168
|
+
var styles15 = StyleSheet17.create({
|
|
15638
16169
|
badgeRow: {
|
|
15639
16170
|
flexDirection: "row",
|
|
15640
16171
|
gap: 8,
|
|
@@ -15790,9 +16321,9 @@ function BugBearButton({
|
|
|
15790
16321
|
return { x, y };
|
|
15791
16322
|
};
|
|
15792
16323
|
const initialPos = getInitialPosition();
|
|
15793
|
-
const pan =
|
|
15794
|
-
const isDragging =
|
|
15795
|
-
const panResponder =
|
|
16324
|
+
const pan = useRef4(new Animated2.ValueXY(initialPos)).current;
|
|
16325
|
+
const isDragging = useRef4(false);
|
|
16326
|
+
const panResponder = useRef4(
|
|
15796
16327
|
PanResponder.create({
|
|
15797
16328
|
onStartShouldSetPanResponder: () => draggable,
|
|
15798
16329
|
onMoveShouldSetPanResponder: (_, gs) => draggable && (Math.abs(gs.dx) > 5 || Math.abs(gs.dy) > 5),
|
|
@@ -15808,7 +16339,7 @@ function BugBearButton({
|
|
|
15808
16339
|
if (Math.abs(gs.dx) > 5 || Math.abs(gs.dy) > 5) {
|
|
15809
16340
|
isDragging.current = true;
|
|
15810
16341
|
}
|
|
15811
|
-
|
|
16342
|
+
Animated2.event(
|
|
15812
16343
|
[null, { dx: pan.x, dy: pan.y }],
|
|
15813
16344
|
{ useNativeDriver: false }
|
|
15814
16345
|
)(_, gs);
|
|
@@ -15821,7 +16352,7 @@ function BugBearButton({
|
|
|
15821
16352
|
const margin = 16;
|
|
15822
16353
|
const snapX = currentX < screenWidth / 2 ? margin : screenWidth - buttonSize - margin;
|
|
15823
16354
|
const snapY = Math.max(minY, Math.min(currentY, screenHeight - maxYOffset));
|
|
15824
|
-
|
|
16355
|
+
Animated2.spring(pan, {
|
|
15825
16356
|
toValue: { x: snapX, y: snapY },
|
|
15826
16357
|
useNativeDriver: false,
|
|
15827
16358
|
friction: 7,
|
|
@@ -15894,50 +16425,50 @@ function BugBearButton({
|
|
|
15894
16425
|
const renderScreen = () => {
|
|
15895
16426
|
switch (currentScreen.name) {
|
|
15896
16427
|
case "HOME":
|
|
15897
|
-
return /* @__PURE__ */
|
|
16428
|
+
return /* @__PURE__ */ React18.createElement(HomeScreen, { nav });
|
|
15898
16429
|
case "TEST_DETAIL":
|
|
15899
|
-
return /* @__PURE__ */
|
|
16430
|
+
return /* @__PURE__ */ React18.createElement(TestDetailScreen, { testId: currentScreen.testId, nav });
|
|
15900
16431
|
case "TEST_LIST":
|
|
15901
|
-
return /* @__PURE__ */
|
|
16432
|
+
return /* @__PURE__ */ React18.createElement(TestListScreen, { nav });
|
|
15902
16433
|
case "TEST_FEEDBACK":
|
|
15903
|
-
return /* @__PURE__ */
|
|
16434
|
+
return /* @__PURE__ */ React18.createElement(TestFeedbackScreen, { status: currentScreen.status, assignmentId: currentScreen.assignmentId, nav });
|
|
15904
16435
|
case "REPORT":
|
|
15905
|
-
return /* @__PURE__ */
|
|
16436
|
+
return /* @__PURE__ */ React18.createElement(ReportScreen, { nav, prefill: currentScreen.prefill });
|
|
15906
16437
|
case "REPORT_SUCCESS":
|
|
15907
|
-
return /* @__PURE__ */
|
|
16438
|
+
return /* @__PURE__ */ React18.createElement(ReportSuccessScreen, { nav });
|
|
15908
16439
|
case "MESSAGE_LIST":
|
|
15909
|
-
return /* @__PURE__ */
|
|
16440
|
+
return /* @__PURE__ */ React18.createElement(MessageListScreen, { nav });
|
|
15910
16441
|
case "THREAD_DETAIL":
|
|
15911
|
-
return /* @__PURE__ */
|
|
16442
|
+
return /* @__PURE__ */ React18.createElement(ThreadDetailScreen, { thread: currentScreen.thread, nav });
|
|
15912
16443
|
case "COMPOSE_MESSAGE":
|
|
15913
|
-
return /* @__PURE__ */
|
|
16444
|
+
return /* @__PURE__ */ React18.createElement(ComposeMessageScreen, { nav });
|
|
15914
16445
|
case "ISSUE_LIST":
|
|
15915
|
-
return /* @__PURE__ */
|
|
16446
|
+
return /* @__PURE__ */ React18.createElement(IssueListScreen, { nav, category: currentScreen.category });
|
|
15916
16447
|
case "ISSUE_DETAIL":
|
|
15917
|
-
return /* @__PURE__ */
|
|
16448
|
+
return /* @__PURE__ */ React18.createElement(IssueDetailScreen, { nav, issue: currentScreen.issue });
|
|
15918
16449
|
case "PROFILE":
|
|
15919
|
-
return /* @__PURE__ */
|
|
16450
|
+
return /* @__PURE__ */ React18.createElement(ProfileScreen, { nav });
|
|
15920
16451
|
default:
|
|
15921
|
-
return /* @__PURE__ */
|
|
16452
|
+
return /* @__PURE__ */ React18.createElement(HomeScreen, { nav });
|
|
15922
16453
|
}
|
|
15923
16454
|
};
|
|
15924
|
-
return /* @__PURE__ */
|
|
15925
|
-
|
|
16455
|
+
return /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement(
|
|
16456
|
+
Animated2.View,
|
|
15926
16457
|
{
|
|
15927
16458
|
style: [styles16.fabContainer, { transform: pan.getTranslateTransform() }, buttonStyle],
|
|
15928
16459
|
...panResponder.panHandlers
|
|
15929
16460
|
},
|
|
15930
|
-
/* @__PURE__ */
|
|
16461
|
+
/* @__PURE__ */ React18.createElement(
|
|
15931
16462
|
TouchableOpacity15,
|
|
15932
16463
|
{
|
|
15933
16464
|
style: styles16.fab,
|
|
15934
16465
|
onPress: () => setModalVisible(true),
|
|
15935
16466
|
activeOpacity: draggable ? 1 : 0.7
|
|
15936
16467
|
},
|
|
15937
|
-
/* @__PURE__ */
|
|
15938
|
-
badgeCount > 0 && /* @__PURE__ */
|
|
16468
|
+
/* @__PURE__ */ React18.createElement(Image4, { source: { uri: BUGBEAR_LOGO_BASE64 }, style: styles16.fabIcon }),
|
|
16469
|
+
badgeCount > 0 && /* @__PURE__ */ React18.createElement(View17, { style: styles16.badge }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.badgeText }, badgeCount > 9 ? "9+" : badgeCount))
|
|
15939
16470
|
)
|
|
15940
|
-
), /* @__PURE__ */
|
|
16471
|
+
), /* @__PURE__ */ React18.createElement(
|
|
15941
16472
|
Modal3,
|
|
15942
16473
|
{
|
|
15943
16474
|
visible: modalVisible,
|
|
@@ -15945,13 +16476,13 @@ function BugBearButton({
|
|
|
15945
16476
|
transparent: true,
|
|
15946
16477
|
onRequestClose: handleClose
|
|
15947
16478
|
},
|
|
15948
|
-
/* @__PURE__ */
|
|
16479
|
+
/* @__PURE__ */ React18.createElement(
|
|
15949
16480
|
KeyboardAvoidingView,
|
|
15950
16481
|
{
|
|
15951
16482
|
behavior: Platform4.OS === "ios" ? "padding" : "height",
|
|
15952
16483
|
style: styles16.modalOverlay
|
|
15953
16484
|
},
|
|
15954
|
-
/* @__PURE__ */
|
|
16485
|
+
/* @__PURE__ */ React18.createElement(View17, { style: styles16.modalContainer }, /* @__PURE__ */ React18.createElement(View17, { style: styles16.header }, /* @__PURE__ */ React18.createElement(View17, { style: styles16.headerLeft }, canGoBack ? /* @__PURE__ */ React18.createElement(View17, { style: styles16.headerNavRow }, /* @__PURE__ */ React18.createElement(TouchableOpacity15, { onPress: () => nav.pop(), style: styles16.backButton }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.backText }, "\u2190 Back")), /* @__PURE__ */ React18.createElement(TouchableOpacity15, { onPress: () => nav.reset(), style: styles16.homeButton }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.homeText }, "\u{1F3E0}"))) : /* @__PURE__ */ React18.createElement(View17, { style: styles16.headerTitleRow }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.headerTitle }, "BugBear"), testerInfo && /* @__PURE__ */ React18.createElement(TouchableOpacity15, { onPress: () => push({ name: "PROFILE" }) }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.headerName }, testerInfo.name, " \u270E")))), getHeaderTitle() ? /* @__PURE__ */ React18.createElement(Text16, { style: styles16.headerScreenTitle, numberOfLines: 1 }, getHeaderTitle()) : null, /* @__PURE__ */ React18.createElement(TouchableOpacity15, { onPress: handleClose, style: styles16.closeButton }, /* @__PURE__ */ React18.createElement(Text16, { style: styles16.closeText }, "\u2715"))), /* @__PURE__ */ React18.createElement(
|
|
15955
16486
|
ScrollView3,
|
|
15956
16487
|
{
|
|
15957
16488
|
style: styles16.content,
|
|
@@ -15959,12 +16490,12 @@ function BugBearButton({
|
|
|
15959
16490
|
keyboardShouldPersistTaps: "handled",
|
|
15960
16491
|
showsVerticalScrollIndicator: false
|
|
15961
16492
|
},
|
|
15962
|
-
isLoading ? /* @__PURE__ */
|
|
16493
|
+
isLoading ? /* @__PURE__ */ React18.createElement(View17, { style: styles16.loadingContainer }, /* @__PURE__ */ React18.createElement(ActivityIndicator2, { size: "large", color: colors.blue }), /* @__PURE__ */ React18.createElement(Text16, { style: styles16.loadingText }, "Loading...")) : renderScreen()
|
|
15963
16494
|
))
|
|
15964
16495
|
)
|
|
15965
16496
|
));
|
|
15966
16497
|
}
|
|
15967
|
-
var styles16 =
|
|
16498
|
+
var styles16 = StyleSheet18.create({
|
|
15968
16499
|
// FAB
|
|
15969
16500
|
fabContainer: {
|
|
15970
16501
|
position: "absolute",
|
|
@@ -16106,8 +16637,8 @@ var styles16 = StyleSheet17.create({
|
|
|
16106
16637
|
});
|
|
16107
16638
|
|
|
16108
16639
|
// src/BugBearErrorBoundary.tsx
|
|
16109
|
-
import
|
|
16110
|
-
import { View as
|
|
16640
|
+
import React19, { Component } from "react";
|
|
16641
|
+
import { View as View18, Text as Text17, TouchableOpacity as TouchableOpacity16, StyleSheet as StyleSheet19 } from "react-native";
|
|
16111
16642
|
var BugBearErrorBoundary = class extends Component {
|
|
16112
16643
|
constructor(props) {
|
|
16113
16644
|
super(props);
|
|
@@ -16152,7 +16683,7 @@ var BugBearErrorBoundary = class extends Component {
|
|
|
16152
16683
|
if (fallback) {
|
|
16153
16684
|
return fallback;
|
|
16154
16685
|
}
|
|
16155
|
-
return /* @__PURE__ */
|
|
16686
|
+
return /* @__PURE__ */ React19.createElement(View18, { style: styles17.container }, /* @__PURE__ */ React19.createElement(Text17, { style: styles17.title }, "Something went wrong"), /* @__PURE__ */ React19.createElement(Text17, { style: styles17.message }, error.message), /* @__PURE__ */ React19.createElement(TouchableOpacity16, { style: styles17.button, onPress: this.reset }, /* @__PURE__ */ React19.createElement(Text17, { style: styles17.buttonText }, "Try Again")), /* @__PURE__ */ React19.createElement(Text17, { style: styles17.caption }, "The error has been captured by BugBear"));
|
|
16156
16687
|
}
|
|
16157
16688
|
return children;
|
|
16158
16689
|
}
|
|
@@ -16163,7 +16694,7 @@ function useErrorContext() {
|
|
|
16163
16694
|
getEnhancedContext: () => contextCapture.getEnhancedContext()
|
|
16164
16695
|
};
|
|
16165
16696
|
}
|
|
16166
|
-
var styles17 =
|
|
16697
|
+
var styles17 = StyleSheet19.create({
|
|
16167
16698
|
container: {
|
|
16168
16699
|
padding: 20,
|
|
16169
16700
|
margin: 20,
|