@bbearai/react-native 0.5.7 → 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 +906 -416
- package/dist/index.mjs +793 -303
- 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,7 +12078,7 @@ 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 [];
|
|
@@ -12050,6 +12337,7 @@ var BugBearClient = class {
|
|
|
12050
12337
|
* This empowers testers to shape better tests over time
|
|
12051
12338
|
*/
|
|
12052
12339
|
async submitTestFeedback(options) {
|
|
12340
|
+
let feedbackPayload;
|
|
12053
12341
|
try {
|
|
12054
12342
|
const testerInfo = await this.getTesterInfo();
|
|
12055
12343
|
if (!testerInfo) {
|
|
@@ -12069,7 +12357,7 @@ var BugBearClient = class {
|
|
|
12069
12357
|
return { success: false, error: `${name} must be between 1 and 5` };
|
|
12070
12358
|
}
|
|
12071
12359
|
}
|
|
12072
|
-
|
|
12360
|
+
feedbackPayload = {
|
|
12073
12361
|
project_id: this.config.projectId,
|
|
12074
12362
|
test_case_id: testCaseId,
|
|
12075
12363
|
assignment_id: assignmentId || null,
|
|
@@ -12087,8 +12375,13 @@ var BugBearClient = class {
|
|
|
12087
12375
|
platform: this.getDeviceInfo().platform,
|
|
12088
12376
|
time_to_complete_seconds: timeToCompleteSeconds || null,
|
|
12089
12377
|
screenshot_urls: screenshotUrls || []
|
|
12090
|
-
}
|
|
12378
|
+
};
|
|
12379
|
+
const { error: feedbackError } = await this.supabase.from("test_feedback").insert(feedbackPayload);
|
|
12091
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
|
+
}
|
|
12092
12385
|
console.error("BugBear: Failed to submit feedback", feedbackError);
|
|
12093
12386
|
return { success: false, error: feedbackError.message };
|
|
12094
12387
|
}
|
|
@@ -12105,6 +12398,10 @@ var BugBearClient = class {
|
|
|
12105
12398
|
return { success: true };
|
|
12106
12399
|
} catch (err) {
|
|
12107
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
|
+
}
|
|
12108
12405
|
console.error("BugBear: Error submitting feedback", err);
|
|
12109
12406
|
return { success: false, error: message };
|
|
12110
12407
|
}
|
|
@@ -12739,6 +13036,7 @@ var BugBearClient = class {
|
|
|
12739
13036
|
* Send a message to a thread
|
|
12740
13037
|
*/
|
|
12741
13038
|
async sendMessage(threadId, content, attachments) {
|
|
13039
|
+
let insertData;
|
|
12742
13040
|
try {
|
|
12743
13041
|
const testerInfo = await this.getTesterInfo();
|
|
12744
13042
|
if (!testerInfo) {
|
|
@@ -12750,7 +13048,7 @@ var BugBearClient = class {
|
|
|
12750
13048
|
console.error("BugBear: Rate limit exceeded for messages");
|
|
12751
13049
|
return false;
|
|
12752
13050
|
}
|
|
12753
|
-
|
|
13051
|
+
insertData = {
|
|
12754
13052
|
thread_id: threadId,
|
|
12755
13053
|
sender_type: "tester",
|
|
12756
13054
|
sender_tester_id: testerInfo.id,
|
|
@@ -12765,12 +13063,21 @@ var BugBearClient = class {
|
|
|
12765
13063
|
}
|
|
12766
13064
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
12767
13065
|
if (error) {
|
|
13066
|
+
if (this._queue && isNetworkError(error.message)) {
|
|
13067
|
+
await this._queue.enqueue("message", insertData);
|
|
13068
|
+
return false;
|
|
13069
|
+
}
|
|
12768
13070
|
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
12769
13071
|
return false;
|
|
12770
13072
|
}
|
|
12771
13073
|
await this.markThreadAsRead(threadId);
|
|
12772
13074
|
return true;
|
|
12773
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
|
+
}
|
|
12774
13081
|
console.error("BugBear: Error sending message", err);
|
|
12775
13082
|
return false;
|
|
12776
13083
|
}
|
|
@@ -12957,7 +13264,7 @@ var BugBearClient = class {
|
|
|
12957
13264
|
console.error("BugBear: Failed to fetch session history", formatPgError(error));
|
|
12958
13265
|
return [];
|
|
12959
13266
|
}
|
|
12960
|
-
return (data || []).map((
|
|
13267
|
+
return (data || []).map((s2) => this.transformSession(s2));
|
|
12961
13268
|
} catch (err) {
|
|
12962
13269
|
console.error("BugBear: Error fetching session history", err);
|
|
12963
13270
|
return [];
|
|
@@ -13105,7 +13412,7 @@ function createBugBear(config) {
|
|
|
13105
13412
|
}
|
|
13106
13413
|
|
|
13107
13414
|
// src/BugBearProvider.tsx
|
|
13108
|
-
import { Platform, Dimensions } from "react-native";
|
|
13415
|
+
import { Platform, Dimensions, AppState } from "react-native";
|
|
13109
13416
|
var BugBearContext = createContext({
|
|
13110
13417
|
client: null,
|
|
13111
13418
|
isTester: false,
|
|
@@ -13146,6 +13453,7 @@ var BugBearContext = createContext({
|
|
|
13146
13453
|
issueCounts: { open: 0, done: 0, reopened: 0 },
|
|
13147
13454
|
refreshIssueCounts: async () => {
|
|
13148
13455
|
},
|
|
13456
|
+
queuedCount: 0,
|
|
13149
13457
|
dashboardUrl: void 0,
|
|
13150
13458
|
onError: void 0
|
|
13151
13459
|
});
|
|
@@ -13162,6 +13470,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13162
13470
|
const [threads, setThreads] = useState([]);
|
|
13163
13471
|
const [unreadCount, setUnreadCount] = useState(0);
|
|
13164
13472
|
const [issueCounts, setIssueCounts] = useState({ open: 0, done: 0, reopened: 0 });
|
|
13473
|
+
const [queuedCount, setQueuedCount] = useState(0);
|
|
13165
13474
|
const [activeSession, setActiveSession] = useState(null);
|
|
13166
13475
|
const [sessionFindings, setSessionFindings] = useState([]);
|
|
13167
13476
|
const hasInitialized = useRef(false);
|
|
@@ -13321,18 +13630,46 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13321
13630
|
hasInitialized.current = true;
|
|
13322
13631
|
contextCapture.startCapture();
|
|
13323
13632
|
const newClient = createBugBear(config);
|
|
13633
|
+
if (newClient.queue) {
|
|
13634
|
+
newClient.queue.onChange(setQueuedCount);
|
|
13635
|
+
newClient.initQueue();
|
|
13636
|
+
}
|
|
13324
13637
|
setClient(newClient);
|
|
13325
13638
|
initializeBugBear(newClient);
|
|
13326
13639
|
}
|
|
13327
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]);
|
|
13328
13653
|
useEffect(() => {
|
|
13329
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;
|
|
13330
13664
|
const interval = setInterval(() => {
|
|
13331
13665
|
refreshThreads();
|
|
13332
13666
|
refreshIssueCounts();
|
|
13333
|
-
},
|
|
13334
|
-
return () =>
|
|
13335
|
-
|
|
13667
|
+
}, pollInterval);
|
|
13668
|
+
return () => {
|
|
13669
|
+
clearInterval(interval);
|
|
13670
|
+
unsubscribe?.();
|
|
13671
|
+
};
|
|
13672
|
+
}, [client, isTester, isQAEnabled, refreshThreads, refreshIssueCounts, refreshAssignments]);
|
|
13336
13673
|
const currentAssignment = assignments.find(
|
|
13337
13674
|
(a) => a.status === "in_progress"
|
|
13338
13675
|
) || assignments.find(
|
|
@@ -13375,6 +13712,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13375
13712
|
// Issue tracking
|
|
13376
13713
|
issueCounts,
|
|
13377
13714
|
refreshIssueCounts,
|
|
13715
|
+
queuedCount,
|
|
13378
13716
|
dashboardUrl: config.dashboardUrl,
|
|
13379
13717
|
onError: config.onError
|
|
13380
13718
|
}
|
|
@@ -13384,21 +13722,21 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
|
|
|
13384
13722
|
}
|
|
13385
13723
|
|
|
13386
13724
|
// src/BugBearButton.tsx
|
|
13387
|
-
import
|
|
13725
|
+
import React18, { useState as useState12, useRef as useRef4 } from "react";
|
|
13388
13726
|
import {
|
|
13389
|
-
View as
|
|
13727
|
+
View as View17,
|
|
13390
13728
|
Text as Text16,
|
|
13391
13729
|
Image as Image4,
|
|
13392
13730
|
TouchableOpacity as TouchableOpacity15,
|
|
13393
13731
|
Modal as Modal3,
|
|
13394
13732
|
ScrollView as ScrollView3,
|
|
13395
|
-
StyleSheet as
|
|
13733
|
+
StyleSheet as StyleSheet18,
|
|
13396
13734
|
Dimensions as Dimensions2,
|
|
13397
13735
|
KeyboardAvoidingView,
|
|
13398
13736
|
Platform as Platform4,
|
|
13399
13737
|
PanResponder,
|
|
13400
|
-
Animated,
|
|
13401
|
-
ActivityIndicator as
|
|
13738
|
+
Animated as Animated2,
|
|
13739
|
+
ActivityIndicator as ActivityIndicator2,
|
|
13402
13740
|
Keyboard as Keyboard2
|
|
13403
13741
|
} from "react-native";
|
|
13404
13742
|
|
|
@@ -13544,9 +13882,9 @@ var shared = StyleSheet.create({
|
|
|
13544
13882
|
function formatElapsedTime(seconds) {
|
|
13545
13883
|
const h = Math.floor(seconds / 3600);
|
|
13546
13884
|
const m = Math.floor(seconds % 3600 / 60);
|
|
13547
|
-
const
|
|
13548
|
-
if (h > 0) return `${h}:${m.toString().padStart(2, "0")}:${
|
|
13549
|
-
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")}`;
|
|
13550
13888
|
}
|
|
13551
13889
|
function formatRelativeTime(dateString) {
|
|
13552
13890
|
const date = new Date(dateString);
|
|
@@ -13618,11 +13956,90 @@ var templateInfo = {
|
|
|
13618
13956
|
};
|
|
13619
13957
|
|
|
13620
13958
|
// src/widget/screens/HomeScreen.tsx
|
|
13621
|
-
import
|
|
13622
|
-
import { View, Text, TouchableOpacity, StyleSheet as
|
|
13623
|
-
|
|
13624
|
-
|
|
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;
|
|
13625
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(() => {
|
|
13626
14043
|
refreshAssignments();
|
|
13627
14044
|
refreshThreads();
|
|
13628
14045
|
refreshIssueCounts();
|
|
@@ -13632,103 +14049,104 @@ function HomeScreen({ nav }) {
|
|
|
13632
14049
|
const retestCount = pendingAssignments.filter((a) => a.isVerification).length;
|
|
13633
14050
|
const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
|
|
13634
14051
|
const totalTests = assignments.length;
|
|
13635
|
-
return /* @__PURE__ */
|
|
14052
|
+
if (isLoading) return /* @__PURE__ */ React3.createElement(HomeScreenSkeleton, null);
|
|
14053
|
+
return /* @__PURE__ */ React3.createElement(View2, null, pendingCount > 0 ? /* @__PURE__ */ React3.createElement(
|
|
13636
14054
|
TouchableOpacity,
|
|
13637
14055
|
{
|
|
13638
14056
|
style: [styles.heroBanner, styles.heroBannerTests],
|
|
13639
14057
|
onPress: () => nav.push({ name: "TEST_DETAIL" }),
|
|
13640
14058
|
activeOpacity: 0.8
|
|
13641
14059
|
},
|
|
13642
|
-
/* @__PURE__ */
|
|
13643
|
-
/* @__PURE__ */
|
|
13644
|
-
retestCount > 0 && /* @__PURE__ */
|
|
13645
|
-
/* @__PURE__ */
|
|
13646
|
-
) : 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(
|
|
13647
14065
|
TouchableOpacity,
|
|
13648
14066
|
{
|
|
13649
14067
|
style: [styles.heroBanner, styles.heroBannerMessages],
|
|
13650
14068
|
onPress: () => nav.push({ name: "MESSAGE_LIST" }),
|
|
13651
14069
|
activeOpacity: 0.8
|
|
13652
14070
|
},
|
|
13653
|
-
/* @__PURE__ */
|
|
13654
|
-
/* @__PURE__ */
|
|
13655
|
-
/* @__PURE__ */
|
|
13656
|
-
) : /* @__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(
|
|
13657
14075
|
TouchableOpacity,
|
|
13658
14076
|
{
|
|
13659
14077
|
style: styles.actionCard,
|
|
13660
14078
|
onPress: () => nav.push({ name: "TEST_LIST" }),
|
|
13661
14079
|
activeOpacity: 0.7
|
|
13662
14080
|
},
|
|
13663
|
-
/* @__PURE__ */
|
|
13664
|
-
/* @__PURE__ */
|
|
13665
|
-
pendingCount > 0 && /* @__PURE__ */
|
|
13666
|
-
), /* @__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(
|
|
13667
14085
|
TouchableOpacity,
|
|
13668
14086
|
{
|
|
13669
14087
|
style: styles.actionCard,
|
|
13670
14088
|
onPress: () => nav.push({ name: "REPORT", prefill: { type: "bug" } }),
|
|
13671
14089
|
activeOpacity: 0.7
|
|
13672
14090
|
},
|
|
13673
|
-
/* @__PURE__ */
|
|
13674
|
-
/* @__PURE__ */
|
|
13675
|
-
), /* @__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(
|
|
13676
14094
|
TouchableOpacity,
|
|
13677
14095
|
{
|
|
13678
14096
|
style: styles.actionCard,
|
|
13679
14097
|
onPress: () => nav.push({ name: "REPORT", prefill: { type: "feedback" } }),
|
|
13680
14098
|
activeOpacity: 0.7
|
|
13681
14099
|
},
|
|
13682
|
-
/* @__PURE__ */
|
|
13683
|
-
/* @__PURE__ */
|
|
13684
|
-
), /* @__PURE__ */
|
|
14100
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.actionIcon }, "\u{1F4A1}"),
|
|
14101
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.actionLabel }, "Feedback")
|
|
14102
|
+
), /* @__PURE__ */ React3.createElement(
|
|
13685
14103
|
TouchableOpacity,
|
|
13686
14104
|
{
|
|
13687
14105
|
style: styles.actionCard,
|
|
13688
14106
|
onPress: () => nav.push({ name: "MESSAGE_LIST" }),
|
|
13689
14107
|
activeOpacity: 0.7
|
|
13690
14108
|
},
|
|
13691
|
-
/* @__PURE__ */
|
|
13692
|
-
/* @__PURE__ */
|
|
13693
|
-
unreadCount > 0 && /* @__PURE__ */
|
|
13694
|
-
)), /* @__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(
|
|
13695
14113
|
TouchableOpacity,
|
|
13696
14114
|
{
|
|
13697
14115
|
style: [styles.issueCard, styles.issueCardOpen],
|
|
13698
14116
|
onPress: () => nav.push({ name: "ISSUE_LIST", category: "open" }),
|
|
13699
14117
|
activeOpacity: 0.7
|
|
13700
14118
|
},
|
|
13701
|
-
/* @__PURE__ */
|
|
13702
|
-
/* @__PURE__ */
|
|
13703
|
-
), /* @__PURE__ */
|
|
14119
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.issueCountOpen }, issueCounts.open),
|
|
14120
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.issueLabel }, "Open")
|
|
14121
|
+
), /* @__PURE__ */ React3.createElement(
|
|
13704
14122
|
TouchableOpacity,
|
|
13705
14123
|
{
|
|
13706
14124
|
style: [styles.issueCard, styles.issueCardDone],
|
|
13707
14125
|
onPress: () => nav.push({ name: "ISSUE_LIST", category: "done" }),
|
|
13708
14126
|
activeOpacity: 0.7
|
|
13709
14127
|
},
|
|
13710
|
-
/* @__PURE__ */
|
|
13711
|
-
/* @__PURE__ */
|
|
13712
|
-
), /* @__PURE__ */
|
|
14128
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.issueCountDone }, issueCounts.done),
|
|
14129
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.issueLabel }, "Done")
|
|
14130
|
+
), /* @__PURE__ */ React3.createElement(
|
|
13713
14131
|
TouchableOpacity,
|
|
13714
14132
|
{
|
|
13715
14133
|
style: [styles.issueCard, styles.issueCardReopened],
|
|
13716
14134
|
onPress: () => nav.push({ name: "ISSUE_LIST", category: "reopened" }),
|
|
13717
14135
|
activeOpacity: 0.7
|
|
13718
14136
|
},
|
|
13719
|
-
/* @__PURE__ */
|
|
13720
|
-
/* @__PURE__ */
|
|
13721
|
-
)), 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(
|
|
13722
14140
|
TouchableOpacity,
|
|
13723
14141
|
{
|
|
13724
14142
|
style: styles.webAppLink,
|
|
13725
14143
|
onPress: () => Linking.openURL(dashboardUrl),
|
|
13726
14144
|
activeOpacity: 0.7
|
|
13727
14145
|
},
|
|
13728
|
-
/* @__PURE__ */
|
|
13729
|
-
/* @__PURE__ */
|
|
13730
|
-
/* @__PURE__ */
|
|
13731
|
-
), /* @__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(
|
|
13732
14150
|
TouchableOpacity,
|
|
13733
14151
|
{
|
|
13734
14152
|
style: styles.refreshButton,
|
|
@@ -13738,10 +14156,10 @@ function HomeScreen({ nav }) {
|
|
|
13738
14156
|
refreshIssueCounts();
|
|
13739
14157
|
}
|
|
13740
14158
|
},
|
|
13741
|
-
/* @__PURE__ */
|
|
14159
|
+
/* @__PURE__ */ React3.createElement(Text, { style: styles.refreshText }, "\u21BB Refresh")
|
|
13742
14160
|
));
|
|
13743
14161
|
}
|
|
13744
|
-
var styles =
|
|
14162
|
+
var styles = StyleSheet3.create({
|
|
13745
14163
|
heroBanner: {
|
|
13746
14164
|
borderRadius: 16,
|
|
13747
14165
|
padding: 24,
|
|
@@ -13965,8 +14383,8 @@ var styles = StyleSheet2.create({
|
|
|
13965
14383
|
});
|
|
13966
14384
|
|
|
13967
14385
|
// src/widget/screens/TestDetailScreen.tsx
|
|
13968
|
-
import
|
|
13969
|
-
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";
|
|
13970
14388
|
function TestDetailScreen({ testId, nav }) {
|
|
13971
14389
|
const { client, assignments, currentAssignment, refreshAssignments, getDeviceInfo, onNavigate } = useBugBear();
|
|
13972
14390
|
const displayedAssignment = testId ? assignments.find((a) => a.id === testId) || currentAssignment : currentAssignment;
|
|
@@ -13979,12 +14397,12 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
13979
14397
|
const [skipNotes, setSkipNotes] = useState2("");
|
|
13980
14398
|
const [skipping, setSkipping] = useState2(false);
|
|
13981
14399
|
const [isSubmitting, setIsSubmitting] = useState2(false);
|
|
13982
|
-
|
|
14400
|
+
useEffect4(() => {
|
|
13983
14401
|
setCriteriaResults({});
|
|
13984
14402
|
setShowSteps(true);
|
|
13985
14403
|
setShowDetails(false);
|
|
13986
14404
|
}, [displayedAssignment?.id]);
|
|
13987
|
-
|
|
14405
|
+
useEffect4(() => {
|
|
13988
14406
|
const active = displayedAssignment?.status === "in_progress" ? displayedAssignment : null;
|
|
13989
14407
|
if (!active?.startedAt) {
|
|
13990
14408
|
setAssignmentElapsedTime(0);
|
|
@@ -14060,14 +14478,14 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
14060
14478
|
}
|
|
14061
14479
|
}, [client, displayedAssignment, selectedSkipReason, skipNotes, refreshAssignments, assignments, nav]);
|
|
14062
14480
|
if (!displayedAssignment) {
|
|
14063
|
-
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")));
|
|
14064
14482
|
}
|
|
14065
14483
|
const testCase = displayedAssignment.testCase;
|
|
14066
14484
|
const template = testCase.track?.testTemplate || "steps";
|
|
14067
14485
|
const steps = testCase.steps;
|
|
14068
14486
|
const info = templateInfo[template] || templateInfo.steps;
|
|
14069
14487
|
const rubricMode = testCase.track?.rubricMode || "pass_fail";
|
|
14070
|
-
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(
|
|
14071
14489
|
TouchableOpacity2,
|
|
14072
14490
|
{
|
|
14073
14491
|
key: idx,
|
|
@@ -14079,31 +14497,31 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
14079
14497
|
}),
|
|
14080
14498
|
style: [styles2.checklistItem, criteriaResults[idx] === true && styles2.checklistItemChecked]
|
|
14081
14499
|
},
|
|
14082
|
-
/* @__PURE__ */
|
|
14083
|
-
/* @__PURE__ */
|
|
14084
|
-
)), 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(
|
|
14085
14503
|
TouchableOpacity2,
|
|
14086
14504
|
{
|
|
14087
14505
|
onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: true })),
|
|
14088
14506
|
style: [styles2.pfButton, criteriaResults[idx] === true && styles2.pfButtonPass]
|
|
14089
14507
|
},
|
|
14090
|
-
/* @__PURE__ */
|
|
14091
|
-
), /* @__PURE__ */
|
|
14508
|
+
/* @__PURE__ */ React4.createElement(Text2, { style: [styles2.pfButtonText, criteriaResults[idx] === true && styles2.pfButtonTextActive] }, "\u2713 Pass")
|
|
14509
|
+
), /* @__PURE__ */ React4.createElement(
|
|
14092
14510
|
TouchableOpacity2,
|
|
14093
14511
|
{
|
|
14094
14512
|
onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: false })),
|
|
14095
14513
|
style: [styles2.pfButton, criteriaResults[idx] === false && styles2.pfButtonFail]
|
|
14096
14514
|
},
|
|
14097
|
-
/* @__PURE__ */
|
|
14098
|
-
)) : /* @__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(
|
|
14099
14517
|
TouchableOpacity2,
|
|
14100
14518
|
{
|
|
14101
14519
|
key: n,
|
|
14102
14520
|
onPress: () => setCriteriaResults((prev) => ({ ...prev, [idx]: n })),
|
|
14103
14521
|
style: [styles2.ratingBtn, criteriaResults[idx] === n && styles2.ratingBtnActive]
|
|
14104
14522
|
},
|
|
14105
|
-
/* @__PURE__ */
|
|
14106
|
-
))))), 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(
|
|
14107
14525
|
TouchableOpacity2,
|
|
14108
14526
|
{
|
|
14109
14527
|
style: styles2.navigateButton,
|
|
@@ -14113,21 +14531,21 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
14113
14531
|
nav.closeWidget?.();
|
|
14114
14532
|
}
|
|
14115
14533
|
},
|
|
14116
|
-
/* @__PURE__ */
|
|
14117
|
-
), /* @__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:"), [
|
|
14118
14536
|
{ reason: "blocked", label: "\u{1F6AB} Blocked by a bug" },
|
|
14119
14537
|
{ reason: "not_ready", label: "\u{1F6A7} Feature not ready" },
|
|
14120
14538
|
{ reason: "dependency", label: "\u{1F517} Needs another test first" },
|
|
14121
14539
|
{ reason: "other", label: "\u{1F4DD} Other reason" }
|
|
14122
|
-
].map(({ reason, label }) => /* @__PURE__ */
|
|
14540
|
+
].map(({ reason, label }) => /* @__PURE__ */ React4.createElement(
|
|
14123
14541
|
TouchableOpacity2,
|
|
14124
14542
|
{
|
|
14125
14543
|
key: reason,
|
|
14126
14544
|
style: [styles2.skipOption, selectedSkipReason === reason && styles2.skipOptionActive],
|
|
14127
14545
|
onPress: () => setSelectedSkipReason(reason)
|
|
14128
14546
|
},
|
|
14129
|
-
/* @__PURE__ */
|
|
14130
|
-
)), /* @__PURE__ */
|
|
14547
|
+
/* @__PURE__ */ React4.createElement(Text2, { style: [styles2.skipOptionText, selectedSkipReason === reason && styles2.skipOptionTextActive] }, label)
|
|
14548
|
+
)), /* @__PURE__ */ React4.createElement(
|
|
14131
14549
|
TextInput,
|
|
14132
14550
|
{
|
|
14133
14551
|
style: styles2.skipNotes,
|
|
@@ -14137,21 +14555,21 @@ function TestDetailScreen({ testId, nav }) {
|
|
|
14137
14555
|
placeholderTextColor: colors.textMuted,
|
|
14138
14556
|
multiline: true
|
|
14139
14557
|
}
|
|
14140
|
-
), /* @__PURE__ */
|
|
14558
|
+
), /* @__PURE__ */ React4.createElement(View3, { style: styles2.skipActions }, /* @__PURE__ */ React4.createElement(TouchableOpacity2, { style: styles2.skipCancel, onPress: () => {
|
|
14141
14559
|
setShowSkipModal(false);
|
|
14142
14560
|
setSelectedSkipReason(null);
|
|
14143
14561
|
setSkipNotes("");
|
|
14144
|
-
} }, /* @__PURE__ */
|
|
14562
|
+
} }, /* @__PURE__ */ React4.createElement(Text2, { style: styles2.skipCancelText }, "Cancel")), /* @__PURE__ */ React4.createElement(
|
|
14145
14563
|
TouchableOpacity2,
|
|
14146
14564
|
{
|
|
14147
14565
|
style: [styles2.skipConfirm, !selectedSkipReason && { opacity: 0.4 }],
|
|
14148
14566
|
onPress: handleSkip,
|
|
14149
14567
|
disabled: !selectedSkipReason || skipping
|
|
14150
14568
|
},
|
|
14151
|
-
/* @__PURE__ */
|
|
14569
|
+
/* @__PURE__ */ React4.createElement(Text2, { style: styles2.skipConfirmText }, skipping ? "Skipping..." : "Skip Test")
|
|
14152
14570
|
))))));
|
|
14153
14571
|
}
|
|
14154
|
-
var styles2 =
|
|
14572
|
+
var styles2 = StyleSheet4.create({
|
|
14155
14573
|
container: { paddingBottom: 16 },
|
|
14156
14574
|
retestBanner: { flexDirection: "row", alignItems: "center", gap: 6, backgroundColor: "#422006", borderWidth: 1, borderColor: "#854d0e", borderRadius: 8, paddingVertical: 6, paddingHorizontal: 10, marginBottom: 10 },
|
|
14157
14575
|
retestIcon: { fontSize: 14 },
|
|
@@ -14246,14 +14664,17 @@ var styles2 = StyleSheet3.create({
|
|
|
14246
14664
|
});
|
|
14247
14665
|
|
|
14248
14666
|
// src/widget/screens/TestListScreen.tsx
|
|
14249
|
-
import
|
|
14250
|
-
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";
|
|
14251
14669
|
function TestListScreen({ nav }) {
|
|
14252
|
-
const { assignments, currentAssignment, refreshAssignments } = useBugBear();
|
|
14670
|
+
const { assignments, currentAssignment, refreshAssignments, isLoading } = useBugBear();
|
|
14253
14671
|
const [filter, setFilter] = useState3("all");
|
|
14254
14672
|
const [roleFilter, setRoleFilter] = useState3(null);
|
|
14673
|
+
const [trackFilter, setTrackFilter] = useState3(null);
|
|
14674
|
+
const [searchQuery, setSearchQuery] = useState3("");
|
|
14675
|
+
const [sortMode, setSortMode] = useState3("priority");
|
|
14255
14676
|
const [collapsedFolders, setCollapsedFolders] = useState3(/* @__PURE__ */ new Set());
|
|
14256
|
-
|
|
14677
|
+
useEffect5(() => {
|
|
14257
14678
|
refreshAssignments();
|
|
14258
14679
|
}, []);
|
|
14259
14680
|
const availableRoles = useMemo2(() => {
|
|
@@ -14263,6 +14684,13 @@ function TestListScreen({ nav }) {
|
|
|
14263
14684
|
}
|
|
14264
14685
|
return Array.from(roleMap.values());
|
|
14265
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]);
|
|
14266
14694
|
const selectedRole = availableRoles.find((r) => r.id === roleFilter);
|
|
14267
14695
|
const groupedAssignments = useMemo2(() => {
|
|
14268
14696
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -14286,6 +14714,8 @@ function TestListScreen({ nav }) {
|
|
|
14286
14714
|
folder.assignments.sort((a, b) => {
|
|
14287
14715
|
if (a.isVerification && !b.isVerification) return -1;
|
|
14288
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;
|
|
14289
14719
|
const sd = (statusOrder[a.status] ?? 5) - (statusOrder[b.status] ?? 5);
|
|
14290
14720
|
if (sd !== 0) return sd;
|
|
14291
14721
|
return (priorityOrder[a.testCase.priority] ?? 4) - (priorityOrder[b.testCase.priority] ?? 4);
|
|
@@ -14297,7 +14727,7 @@ function TestListScreen({ nav }) {
|
|
|
14297
14727
|
if (!b.group) return -1;
|
|
14298
14728
|
return a.group.sortOrder - b.group.sortOrder;
|
|
14299
14729
|
});
|
|
14300
|
-
}, [assignments]);
|
|
14730
|
+
}, [assignments, sortMode]);
|
|
14301
14731
|
const toggleFolder = useCallback3((id) => {
|
|
14302
14732
|
setCollapsedFolders((prev) => {
|
|
14303
14733
|
const next = new Set(prev);
|
|
@@ -14306,28 +14736,36 @@ function TestListScreen({ nav }) {
|
|
|
14306
14736
|
return next;
|
|
14307
14737
|
});
|
|
14308
14738
|
}, []);
|
|
14309
|
-
const filterAssignment = (a) => {
|
|
14739
|
+
const filterAssignment = useCallback3((a) => {
|
|
14310
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
|
+
}
|
|
14311
14748
|
if (filter === "pending") return a.status === "pending" || a.status === "in_progress";
|
|
14312
14749
|
if (filter === "done") return a.status === "passed";
|
|
14313
14750
|
if (filter === "reopened") return a.status === "failed";
|
|
14314
14751
|
return true;
|
|
14315
|
-
};
|
|
14316
|
-
|
|
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 }, [
|
|
14317
14755
|
{ key: "all", label: "All", count: assignments.length },
|
|
14318
14756
|
{ key: "pending", label: "To Do", count: assignments.filter((a) => a.status === "pending" || a.status === "in_progress").length },
|
|
14319
14757
|
{ key: "done", label: "Done", count: assignments.filter((a) => a.status === "passed").length },
|
|
14320
14758
|
{ key: "reopened", label: "Re Opened", count: assignments.filter((a) => a.status === "failed").length }
|
|
14321
|
-
].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(
|
|
14322
14760
|
TouchableOpacity3,
|
|
14323
14761
|
{
|
|
14324
14762
|
style: [styles3.roleBtn, !roleFilter && styles3.roleBtnActive],
|
|
14325
14763
|
onPress: () => setRoleFilter(null)
|
|
14326
14764
|
},
|
|
14327
|
-
/* @__PURE__ */
|
|
14765
|
+
/* @__PURE__ */ React5.createElement(Text3, { style: [styles3.roleBtnText, !roleFilter && styles3.roleBtnTextActive] }, "All Roles")
|
|
14328
14766
|
), availableRoles.map((role) => {
|
|
14329
14767
|
const isActive = roleFilter === role.id;
|
|
14330
|
-
return /* @__PURE__ */
|
|
14768
|
+
return /* @__PURE__ */ React5.createElement(
|
|
14331
14769
|
TouchableOpacity3,
|
|
14332
14770
|
{
|
|
14333
14771
|
key: role.id,
|
|
@@ -14337,33 +14775,72 @@ function TestListScreen({ nav }) {
|
|
|
14337
14775
|
],
|
|
14338
14776
|
onPress: () => setRoleFilter(isActive ? null : role.id)
|
|
14339
14777
|
},
|
|
14340
|
-
/* @__PURE__ */
|
|
14341
|
-
/* @__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)
|
|
14342
14780
|
);
|
|
14343
|
-
})), 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) => {
|
|
14344
14821
|
const folderId = folder.group?.id || "ungrouped";
|
|
14345
14822
|
const isCollapsed = collapsedFolders.has(folderId);
|
|
14346
14823
|
const filtered = folder.assignments.filter(filterAssignment);
|
|
14347
14824
|
if (filtered.length === 0 && filter !== "all") return null;
|
|
14348
|
-
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) => {
|
|
14349
14826
|
const badge = getStatusBadge(assignment.status);
|
|
14350
14827
|
const isCurrent = currentAssignment?.id === assignment.id;
|
|
14351
|
-
return /* @__PURE__ */
|
|
14828
|
+
return /* @__PURE__ */ React5.createElement(
|
|
14352
14829
|
TouchableOpacity3,
|
|
14353
14830
|
{
|
|
14354
14831
|
key: assignment.id,
|
|
14355
14832
|
style: [styles3.testItem, isCurrent && styles3.testItemCurrent],
|
|
14356
14833
|
onPress: () => nav.push({ name: "TEST_DETAIL", testId: assignment.id })
|
|
14357
14834
|
},
|
|
14358
|
-
/* @__PURE__ */
|
|
14359
|
-
/* @__PURE__ */
|
|
14360
|
-
/* @__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: [
|
|
14361
14838
|
styles3.statusPill,
|
|
14362
14839
|
{
|
|
14363
14840
|
backgroundColor: assignment.status === "passed" ? "#14532d" : assignment.status === "failed" ? "#450a0a" : assignment.status === "in_progress" ? "#172554" : "#27272a",
|
|
14364
14841
|
borderColor: assignment.status === "passed" ? "#166534" : assignment.status === "failed" ? "#7f1d1d" : assignment.status === "in_progress" ? "#1e3a5f" : "#3f3f46"
|
|
14365
14842
|
}
|
|
14366
|
-
] }, /* @__PURE__ */
|
|
14843
|
+
] }, /* @__PURE__ */ React5.createElement(Text3, { style: [
|
|
14367
14844
|
styles3.statusPillText,
|
|
14368
14845
|
{
|
|
14369
14846
|
color: assignment.status === "passed" ? "#4ade80" : assignment.status === "failed" ? "#f87171" : assignment.status === "in_progress" ? "#60a5fa" : "#d4d4d8"
|
|
@@ -14371,9 +14848,9 @@ function TestListScreen({ nav }) {
|
|
|
14371
14848
|
] }, badge.label))
|
|
14372
14849
|
);
|
|
14373
14850
|
}));
|
|
14374
|
-
}), /* @__PURE__ */
|
|
14851
|
+
}), /* @__PURE__ */ React5.createElement(TouchableOpacity3, { style: styles3.refreshBtn, onPress: refreshAssignments }, /* @__PURE__ */ React5.createElement(Text3, { style: styles3.refreshText }, "\u21BB", " Refresh")));
|
|
14375
14852
|
}
|
|
14376
|
-
var styles3 =
|
|
14853
|
+
var styles3 = StyleSheet5.create({
|
|
14377
14854
|
filterBar: { flexDirection: "row", gap: 8, marginBottom: 8 },
|
|
14378
14855
|
filterBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
|
|
14379
14856
|
filterBtnActive: { backgroundColor: colors.blue, borderColor: colors.blue },
|
|
@@ -14408,13 +14885,25 @@ var styles3 = StyleSheet4.create({
|
|
|
14408
14885
|
testMeta: { fontSize: 11, color: colors.textDim },
|
|
14409
14886
|
statusPill: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 6, borderWidth: 1, marginLeft: 8 },
|
|
14410
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" },
|
|
14411
14900
|
refreshBtn: { alignItems: "center", paddingVertical: 12 },
|
|
14412
14901
|
refreshText: { fontSize: 13, color: colors.blue }
|
|
14413
14902
|
});
|
|
14414
14903
|
|
|
14415
14904
|
// src/widget/screens/TestFeedbackScreen.tsx
|
|
14416
|
-
import
|
|
14417
|
-
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";
|
|
14418
14907
|
|
|
14419
14908
|
// src/widget/useImageAttachments.ts
|
|
14420
14909
|
import { useState as useState4, useCallback as useCallback4 } from "react";
|
|
@@ -14514,17 +15003,17 @@ function useImageAttachments(uploadFn, maxImages, bucket = "screenshots") {
|
|
|
14514
15003
|
}
|
|
14515
15004
|
|
|
14516
15005
|
// src/widget/ImagePickerButtons.tsx
|
|
14517
|
-
import
|
|
14518
|
-
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";
|
|
14519
15008
|
|
|
14520
15009
|
// src/widget/ImagePreviewStrip.tsx
|
|
14521
|
-
import
|
|
14522
|
-
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";
|
|
14523
15012
|
function ImagePreviewStrip({ images, onRemove }) {
|
|
14524
15013
|
if (images.length === 0) return null;
|
|
14525
|
-
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")))));
|
|
14526
15015
|
}
|
|
14527
|
-
var styles4 =
|
|
15016
|
+
var styles4 = StyleSheet6.create({
|
|
14528
15017
|
strip: {
|
|
14529
15018
|
flexDirection: "row",
|
|
14530
15019
|
marginTop: 4
|
|
@@ -14543,7 +15032,7 @@ var styles4 = StyleSheet5.create({
|
|
|
14543
15032
|
borderRadius: 8
|
|
14544
15033
|
},
|
|
14545
15034
|
thumbOverlay: {
|
|
14546
|
-
...
|
|
15035
|
+
...StyleSheet6.absoluteFillObject,
|
|
14547
15036
|
backgroundColor: "rgba(0,0,0,0.5)",
|
|
14548
15037
|
justifyContent: "center",
|
|
14549
15038
|
alignItems: "center",
|
|
@@ -14578,25 +15067,25 @@ var styles4 = StyleSheet5.create({
|
|
|
14578
15067
|
// src/widget/ImagePickerButtons.tsx
|
|
14579
15068
|
function ImagePickerButtons({ images, maxImages, onPickGallery, onPickCamera, onRemove, label }) {
|
|
14580
15069
|
if (!IMAGE_PICKER_AVAILABLE) return null;
|
|
14581
|
-
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(
|
|
14582
15071
|
TouchableOpacity5,
|
|
14583
15072
|
{
|
|
14584
15073
|
style: styles5.pickButton,
|
|
14585
15074
|
onPress: onPickGallery,
|
|
14586
15075
|
disabled: images.length >= maxImages
|
|
14587
15076
|
},
|
|
14588
|
-
/* @__PURE__ */
|
|
14589
|
-
), /* @__PURE__ */
|
|
15077
|
+
/* @__PURE__ */ React7.createElement(Text5, { style: [styles5.pickButtonText, images.length >= maxImages && styles5.pickButtonDisabled] }, "Gallery")
|
|
15078
|
+
), /* @__PURE__ */ React7.createElement(
|
|
14590
15079
|
TouchableOpacity5,
|
|
14591
15080
|
{
|
|
14592
15081
|
style: styles5.pickButton,
|
|
14593
15082
|
onPress: onPickCamera,
|
|
14594
15083
|
disabled: images.length >= maxImages
|
|
14595
15084
|
},
|
|
14596
|
-
/* @__PURE__ */
|
|
14597
|
-
), /* @__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 }));
|
|
14598
15087
|
}
|
|
14599
|
-
var styles5 =
|
|
15088
|
+
var styles5 = StyleSheet7.create({
|
|
14600
15089
|
section: {
|
|
14601
15090
|
marginTop: 12,
|
|
14602
15091
|
marginBottom: 4
|
|
@@ -14697,22 +15186,22 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
|
|
|
14697
15186
|
}
|
|
14698
15187
|
}
|
|
14699
15188
|
};
|
|
14700
|
-
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?"), [
|
|
14701
15190
|
{ key: "isOutdated", label: "Test is outdated" },
|
|
14702
15191
|
{ key: "needsMoreDetail", label: "Needs more detail" },
|
|
14703
15192
|
{ key: "stepsUnclear", label: "Steps are unclear" },
|
|
14704
15193
|
{ key: "expectedResultUnclear", label: "Expected result unclear" }
|
|
14705
|
-
].map(({ key, label }) => /* @__PURE__ */
|
|
15194
|
+
].map(({ key, label }) => /* @__PURE__ */ React8.createElement(
|
|
14706
15195
|
TouchableOpacity6,
|
|
14707
15196
|
{
|
|
14708
15197
|
key,
|
|
14709
15198
|
style: [styles6.flagItem, flags[key] && styles6.flagItemActive],
|
|
14710
15199
|
onPress: () => setFlags((prev) => ({ ...prev, [key]: !prev[key] }))
|
|
14711
15200
|
},
|
|
14712
|
-
/* @__PURE__ */
|
|
14713
|
-
/* @__PURE__ */
|
|
14714
|
-
))), /* @__PURE__ */
|
|
14715
|
-
|
|
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,
|
|
14716
15205
|
{
|
|
14717
15206
|
style: styles6.noteInput,
|
|
14718
15207
|
value: note,
|
|
@@ -14721,7 +15210,7 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
|
|
|
14721
15210
|
placeholderTextColor: colors.textMuted,
|
|
14722
15211
|
multiline: true
|
|
14723
15212
|
}
|
|
14724
|
-
), /* @__PURE__ */
|
|
15213
|
+
), /* @__PURE__ */ React8.createElement(
|
|
14725
15214
|
ImagePickerButtons,
|
|
14726
15215
|
{
|
|
14727
15216
|
images: images.images,
|
|
@@ -14731,9 +15220,9 @@ function TestFeedbackScreen({ status, assignmentId, nav }) {
|
|
|
14731
15220
|
onRemove: images.removeImage,
|
|
14732
15221
|
label: "Screenshots (optional)"
|
|
14733
15222
|
}
|
|
14734
|
-
), /* @__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"))));
|
|
14735
15224
|
}
|
|
14736
|
-
var styles6 =
|
|
15225
|
+
var styles6 = StyleSheet8.create({
|
|
14737
15226
|
container: { paddingTop: 8 },
|
|
14738
15227
|
header: { fontSize: 22, fontWeight: "700", color: colors.textPrimary, textAlign: "center", marginBottom: 4 },
|
|
14739
15228
|
subheader: { fontSize: 14, color: colors.textMuted, textAlign: "center", marginBottom: 20 },
|
|
@@ -14757,12 +15246,12 @@ var styles6 = StyleSheet7.create({
|
|
|
14757
15246
|
});
|
|
14758
15247
|
|
|
14759
15248
|
// src/widget/screens/ReportScreen.tsx
|
|
14760
|
-
import
|
|
14761
|
-
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";
|
|
14762
15251
|
|
|
14763
15252
|
// src/widget/CategoryPicker.tsx
|
|
14764
|
-
import
|
|
14765
|
-
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";
|
|
14766
15255
|
var categoryOptions = [
|
|
14767
15256
|
{ value: "ui_ux", label: "UI/UX", icon: "\u{1F3A8}" },
|
|
14768
15257
|
{ value: "functional", label: "Functional", icon: "\u2699\uFE0F" },
|
|
@@ -14777,15 +15266,15 @@ function CategoryPicker({ value, onChange, optional = true }) {
|
|
|
14777
15266
|
onChange(category);
|
|
14778
15267
|
setModalVisible(false);
|
|
14779
15268
|
};
|
|
14780
|
-
return /* @__PURE__ */
|
|
15269
|
+
return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(
|
|
14781
15270
|
TouchableOpacity7,
|
|
14782
15271
|
{
|
|
14783
15272
|
style: styles7.trigger,
|
|
14784
15273
|
onPress: () => setModalVisible(true)
|
|
14785
15274
|
},
|
|
14786
|
-
/* @__PURE__ */
|
|
14787
|
-
/* @__PURE__ */
|
|
14788
|
-
), /* @__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(
|
|
14789
15278
|
Modal2,
|
|
14790
15279
|
{
|
|
14791
15280
|
visible: modalVisible,
|
|
@@ -14793,42 +15282,42 @@ function CategoryPicker({ value, onChange, optional = true }) {
|
|
|
14793
15282
|
animationType: "fade",
|
|
14794
15283
|
onRequestClose: () => setModalVisible(false)
|
|
14795
15284
|
},
|
|
14796
|
-
/* @__PURE__ */
|
|
15285
|
+
/* @__PURE__ */ React9.createElement(
|
|
14797
15286
|
TouchableOpacity7,
|
|
14798
15287
|
{
|
|
14799
15288
|
style: styles7.overlay,
|
|
14800
15289
|
activeOpacity: 1,
|
|
14801
15290
|
onPress: () => setModalVisible(false)
|
|
14802
15291
|
},
|
|
14803
|
-
/* @__PURE__ */
|
|
15292
|
+
/* @__PURE__ */ React9.createElement(View8, { style: styles7.modal, onStartShouldSetResponder: () => true }, /* @__PURE__ */ React9.createElement(Text7, { style: styles7.modalTitle }, "Select Category"), optional && /* @__PURE__ */ React9.createElement(
|
|
14804
15293
|
TouchableOpacity7,
|
|
14805
15294
|
{
|
|
14806
15295
|
style: [styles7.option, !value && styles7.optionSelected],
|
|
14807
15296
|
onPress: () => handleSelect(null)
|
|
14808
15297
|
},
|
|
14809
|
-
/* @__PURE__ */
|
|
14810
|
-
), 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(
|
|
14811
15300
|
TouchableOpacity7,
|
|
14812
15301
|
{
|
|
14813
15302
|
key: optValue,
|
|
14814
15303
|
style: [styles7.option, value === optValue && styles7.optionSelected],
|
|
14815
15304
|
onPress: () => handleSelect(optValue)
|
|
14816
15305
|
},
|
|
14817
|
-
/* @__PURE__ */
|
|
14818
|
-
/* @__PURE__ */
|
|
14819
|
-
value === optValue && /* @__PURE__ */
|
|
14820
|
-
)), /* @__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(
|
|
14821
15310
|
TouchableOpacity7,
|
|
14822
15311
|
{
|
|
14823
15312
|
style: styles7.cancelButton,
|
|
14824
15313
|
onPress: () => setModalVisible(false)
|
|
14825
15314
|
},
|
|
14826
|
-
/* @__PURE__ */
|
|
15315
|
+
/* @__PURE__ */ React9.createElement(Text7, { style: styles7.cancelText }, "Cancel")
|
|
14827
15316
|
))
|
|
14828
15317
|
)
|
|
14829
15318
|
));
|
|
14830
15319
|
}
|
|
14831
|
-
var styles7 =
|
|
15320
|
+
var styles7 = StyleSheet9.create({
|
|
14832
15321
|
trigger: {
|
|
14833
15322
|
flexDirection: "row",
|
|
14834
15323
|
alignItems: "center",
|
|
@@ -14925,11 +15414,11 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14925
15414
|
const [affectedScreen, setAffectedScreen] = useState7("");
|
|
14926
15415
|
const [submitting, setSubmitting] = useState7(false);
|
|
14927
15416
|
const [error, setError] = useState7(null);
|
|
14928
|
-
const submittingRef =
|
|
15417
|
+
const submittingRef = useRef3(false);
|
|
14929
15418
|
const images = useImageAttachments(uploadImage, 5, "screenshots");
|
|
14930
15419
|
const isRetestFailure = prefill?.type === "test_fail";
|
|
14931
15420
|
const isBugType = reportType === "bug" || reportType === "test_fail";
|
|
14932
|
-
|
|
15421
|
+
useEffect6(() => {
|
|
14933
15422
|
if (reportType === "feedback" || reportType === "suggestion") {
|
|
14934
15423
|
setCategory("other");
|
|
14935
15424
|
} else {
|
|
@@ -14978,21 +15467,21 @@ function ReportScreen({ nav, prefill }) {
|
|
|
14978
15467
|
submittingRef.current = false;
|
|
14979
15468
|
}
|
|
14980
15469
|
};
|
|
14981
|
-
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 }, [
|
|
14982
15471
|
{ sev: "critical", color: "#ef4444" },
|
|
14983
15472
|
{ sev: "high", color: "#f97316" },
|
|
14984
15473
|
{ sev: "medium", color: "#eab308" },
|
|
14985
15474
|
{ sev: "low", color: "#6b7280" }
|
|
14986
|
-
].map(({ sev, color }) => /* @__PURE__ */
|
|
15475
|
+
].map(({ sev, color }) => /* @__PURE__ */ React10.createElement(
|
|
14987
15476
|
TouchableOpacity8,
|
|
14988
15477
|
{
|
|
14989
15478
|
key: sev,
|
|
14990
15479
|
style: [styles8.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
|
|
14991
15480
|
onPress: () => setSeverity(sev)
|
|
14992
15481
|
},
|
|
14993
|
-
/* @__PURE__ */
|
|
14994
|
-
)))), /* @__PURE__ */
|
|
14995
|
-
|
|
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,
|
|
14996
15485
|
{
|
|
14997
15486
|
style: styles8.descInput,
|
|
14998
15487
|
value: description,
|
|
@@ -15003,7 +15492,7 @@ function ReportScreen({ nav, prefill }) {
|
|
|
15003
15492
|
numberOfLines: 4,
|
|
15004
15493
|
textAlignVertical: "top"
|
|
15005
15494
|
}
|
|
15006
|
-
)), /* @__PURE__ */
|
|
15495
|
+
)), /* @__PURE__ */ React10.createElement(
|
|
15007
15496
|
ImagePickerButtons,
|
|
15008
15497
|
{
|
|
15009
15498
|
images: images.images,
|
|
@@ -15013,42 +15502,42 @@ function ReportScreen({ nav, prefill }) {
|
|
|
15013
15502
|
onRemove: images.removeImage,
|
|
15014
15503
|
label: "Attachments (optional)"
|
|
15015
15504
|
}
|
|
15016
|
-
), error && /* @__PURE__ */
|
|
15505
|
+
), error && /* @__PURE__ */ React10.createElement(View9, { style: styles8.errorBanner }, /* @__PURE__ */ React10.createElement(Text8, { style: styles8.errorText }, error)), /* @__PURE__ */ React10.createElement(
|
|
15017
15506
|
TouchableOpacity8,
|
|
15018
15507
|
{
|
|
15019
15508
|
style: [shared.primaryButton, styles8.retestSubmitButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
|
|
15020
15509
|
onPress: handleSubmit,
|
|
15021
15510
|
disabled: !description.trim() || submitting || images.isUploading
|
|
15022
15511
|
},
|
|
15023
|
-
/* @__PURE__ */
|
|
15024
|
-
)) : /* @__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 }, [
|
|
15025
15514
|
{ type: "bug", label: "Bug", icon: "\u{1F41B}" },
|
|
15026
15515
|
{ type: "feedback", label: "Feedback", icon: "\u{1F4A1}" },
|
|
15027
15516
|
{ type: "suggestion", label: "Idea", icon: "\u2728" }
|
|
15028
|
-
].map(({ type, label, icon }) => /* @__PURE__ */
|
|
15517
|
+
].map(({ type, label, icon }) => /* @__PURE__ */ React10.createElement(
|
|
15029
15518
|
TouchableOpacity8,
|
|
15030
15519
|
{
|
|
15031
15520
|
key: type,
|
|
15032
15521
|
style: [styles8.typeCard, reportType === type && styles8.typeCardActive],
|
|
15033
15522
|
onPress: () => setReportType(type)
|
|
15034
15523
|
},
|
|
15035
|
-
/* @__PURE__ */
|
|
15036
|
-
/* @__PURE__ */
|
|
15037
|
-
))), 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 }, [
|
|
15038
15527
|
{ sev: "critical", color: "#ef4444" },
|
|
15039
15528
|
{ sev: "high", color: "#f97316" },
|
|
15040
15529
|
{ sev: "medium", color: "#eab308" },
|
|
15041
15530
|
{ sev: "low", color: "#6b7280" }
|
|
15042
|
-
].map(({ sev, color }) => /* @__PURE__ */
|
|
15531
|
+
].map(({ sev, color }) => /* @__PURE__ */ React10.createElement(
|
|
15043
15532
|
TouchableOpacity8,
|
|
15044
15533
|
{
|
|
15045
15534
|
key: sev,
|
|
15046
15535
|
style: [styles8.sevButton, severity === sev && { backgroundColor: `${color}30`, borderColor: color }],
|
|
15047
15536
|
onPress: () => setSeverity(sev)
|
|
15048
15537
|
},
|
|
15049
|
-
/* @__PURE__ */
|
|
15050
|
-
)))), isBugType && /* @__PURE__ */
|
|
15051
|
-
|
|
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,
|
|
15052
15541
|
{
|
|
15053
15542
|
style: styles8.descInput,
|
|
15054
15543
|
value: description,
|
|
@@ -15059,8 +15548,8 @@ function ReportScreen({ nav, prefill }) {
|
|
|
15059
15548
|
numberOfLines: 4,
|
|
15060
15549
|
textAlignVertical: "top"
|
|
15061
15550
|
}
|
|
15062
|
-
)), isBugType && /* @__PURE__ */
|
|
15063
|
-
|
|
15551
|
+
)), isBugType && /* @__PURE__ */ React10.createElement(View9, { style: styles8.section }, /* @__PURE__ */ React10.createElement(Text8, { style: shared.label }, "Which screen?"), /* @__PURE__ */ React10.createElement(
|
|
15552
|
+
TextInput4,
|
|
15064
15553
|
{
|
|
15065
15554
|
style: styles8.screenInput,
|
|
15066
15555
|
value: affectedScreen,
|
|
@@ -15068,7 +15557,7 @@ function ReportScreen({ nav, prefill }) {
|
|
|
15068
15557
|
placeholder: "e.g. Reservations, Settings...",
|
|
15069
15558
|
placeholderTextColor: colors.textMuted
|
|
15070
15559
|
}
|
|
15071
|
-
), /* @__PURE__ */
|
|
15560
|
+
), /* @__PURE__ */ React10.createElement(Text8, { style: styles8.screenHint }, "Which screen or area was the bug on? (optional)")), /* @__PURE__ */ React10.createElement(
|
|
15072
15561
|
ImagePickerButtons,
|
|
15073
15562
|
{
|
|
15074
15563
|
images: images.images,
|
|
@@ -15078,17 +15567,17 @@ function ReportScreen({ nav, prefill }) {
|
|
|
15078
15567
|
onRemove: images.removeImage,
|
|
15079
15568
|
label: "Screenshots (optional)"
|
|
15080
15569
|
}
|
|
15081
|
-
), error && /* @__PURE__ */
|
|
15570
|
+
), error && /* @__PURE__ */ React10.createElement(View9, { style: styles8.errorBanner }, /* @__PURE__ */ React10.createElement(Text8, { style: styles8.errorText }, error)), /* @__PURE__ */ React10.createElement(
|
|
15082
15571
|
TouchableOpacity8,
|
|
15083
15572
|
{
|
|
15084
15573
|
style: [shared.primaryButton, (!description.trim() || submitting || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
|
|
15085
15574
|
onPress: handleSubmit,
|
|
15086
15575
|
disabled: !description.trim() || submitting || images.isUploading
|
|
15087
15576
|
},
|
|
15088
|
-
/* @__PURE__ */
|
|
15577
|
+
/* @__PURE__ */ React10.createElement(Text8, { style: shared.primaryButtonText }, images.isUploading ? "Uploading images..." : submitting ? "Submitting..." : error ? "Retry" : "Submit Report")
|
|
15089
15578
|
)));
|
|
15090
15579
|
}
|
|
15091
|
-
var styles8 =
|
|
15580
|
+
var styles8 = StyleSheet10.create({
|
|
15092
15581
|
typeRow: { flexDirection: "row", gap: 10, marginBottom: 20 },
|
|
15093
15582
|
typeCard: { flex: 1, alignItems: "center", paddingVertical: 16, borderRadius: 12, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border },
|
|
15094
15583
|
typeCardActive: { borderColor: colors.blue, backgroundColor: "#172554" },
|
|
@@ -15112,16 +15601,16 @@ var styles8 = StyleSheet9.create({
|
|
|
15112
15601
|
});
|
|
15113
15602
|
|
|
15114
15603
|
// src/widget/screens/ReportSuccessScreen.tsx
|
|
15115
|
-
import
|
|
15116
|
-
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";
|
|
15117
15606
|
function ReportSuccessScreen({ nav }) {
|
|
15118
|
-
|
|
15607
|
+
useEffect7(() => {
|
|
15119
15608
|
const timer = setTimeout(() => nav.reset(), 2e3);
|
|
15120
15609
|
return () => clearTimeout(timer);
|
|
15121
15610
|
}, [nav]);
|
|
15122
|
-
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"));
|
|
15123
15612
|
}
|
|
15124
|
-
var styles9 =
|
|
15613
|
+
var styles9 = StyleSheet11.create({
|
|
15125
15614
|
container: { alignItems: "center", paddingVertical: 60 },
|
|
15126
15615
|
emoji: { fontSize: 48, marginBottom: 16 },
|
|
15127
15616
|
title: { fontSize: 22, fontWeight: "700", color: colors.textPrimary, marginBottom: 6 },
|
|
@@ -15129,29 +15618,30 @@ var styles9 = StyleSheet10.create({
|
|
|
15129
15618
|
});
|
|
15130
15619
|
|
|
15131
15620
|
// src/widget/screens/MessageListScreen.tsx
|
|
15132
|
-
import
|
|
15133
|
-
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";
|
|
15134
15623
|
function MessageListScreen({ nav }) {
|
|
15135
|
-
const { threads, unreadCount, refreshThreads } = useBugBear();
|
|
15136
|
-
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(
|
|
15137
15627
|
TouchableOpacity9,
|
|
15138
15628
|
{
|
|
15139
15629
|
style: styles10.newMsgButton,
|
|
15140
15630
|
onPress: () => nav.push({ name: "COMPOSE_MESSAGE" })
|
|
15141
15631
|
},
|
|
15142
|
-
/* @__PURE__ */
|
|
15143
|
-
), 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(
|
|
15144
15634
|
TouchableOpacity9,
|
|
15145
15635
|
{
|
|
15146
15636
|
key: thread.id,
|
|
15147
15637
|
style: [styles10.threadItem, thread.unreadCount > 0 && styles10.threadItemUnread],
|
|
15148
15638
|
onPress: () => nav.push({ name: "THREAD_DETAIL", thread })
|
|
15149
15639
|
},
|
|
15150
|
-
/* @__PURE__ */
|
|
15151
|
-
/* @__PURE__ */
|
|
15152
|
-
))), /* @__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"))));
|
|
15153
15643
|
}
|
|
15154
|
-
var styles10 =
|
|
15644
|
+
var styles10 = StyleSheet12.create({
|
|
15155
15645
|
newMsgButton: { backgroundColor: colors.blue, paddingVertical: 12, borderRadius: 12, alignItems: "center", marginBottom: 16 },
|
|
15156
15646
|
newMsgText: { fontSize: 15, fontWeight: "600", color: "#fff" },
|
|
15157
15647
|
threadItem: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 12, paddingHorizontal: 12, borderRadius: 10, marginBottom: 4, backgroundColor: colors.card },
|
|
@@ -15174,8 +15664,8 @@ var styles10 = StyleSheet11.create({
|
|
|
15174
15664
|
});
|
|
15175
15665
|
|
|
15176
15666
|
// src/widget/screens/ThreadDetailScreen.tsx
|
|
15177
|
-
import
|
|
15178
|
-
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";
|
|
15179
15669
|
function ThreadDetailScreen({ thread, nav }) {
|
|
15180
15670
|
const { getThreadMessages, sendMessage, markAsRead, uploadImage } = useBugBear();
|
|
15181
15671
|
const [messages, setMessages] = useState8([]);
|
|
@@ -15184,7 +15674,7 @@ function ThreadDetailScreen({ thread, nav }) {
|
|
|
15184
15674
|
const [sending, setSending] = useState8(false);
|
|
15185
15675
|
const [sendError, setSendError] = useState8(false);
|
|
15186
15676
|
const replyImages = useImageAttachments(uploadImage, 3, "discussion-attachments");
|
|
15187
|
-
|
|
15677
|
+
useEffect8(() => {
|
|
15188
15678
|
let cancelled = false;
|
|
15189
15679
|
setLoading(true);
|
|
15190
15680
|
(async () => {
|
|
@@ -15229,18 +15719,18 @@ function ThreadDetailScreen({ thread, nav }) {
|
|
|
15229
15719
|
}
|
|
15230
15720
|
setSending(false);
|
|
15231
15721
|
};
|
|
15232
|
-
return /* @__PURE__ */
|
|
15233
|
-
|
|
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,
|
|
15234
15724
|
{
|
|
15235
15725
|
key: msg.id,
|
|
15236
15726
|
style: [styles11.bubble, msg.senderType === "tester" ? styles11.bubbleTester : styles11.bubbleAdmin]
|
|
15237
15727
|
},
|
|
15238
|
-
/* @__PURE__ */
|
|
15239
|
-
/* @__PURE__ */
|
|
15240
|
-
msg.attachments && msg.attachments.length > 0 && /* @__PURE__ */
|
|
15241
|
-
/* @__PURE__ */
|
|
15242
|
-
))), sendError && /* @__PURE__ */
|
|
15243
|
-
|
|
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,
|
|
15244
15734
|
{
|
|
15245
15735
|
style: styles11.replyInput,
|
|
15246
15736
|
value: replyText,
|
|
@@ -15250,17 +15740,17 @@ function ThreadDetailScreen({ thread, nav }) {
|
|
|
15250
15740
|
multiline: true,
|
|
15251
15741
|
maxLength: 1e3
|
|
15252
15742
|
}
|
|
15253
|
-
), /* @__PURE__ */
|
|
15743
|
+
), /* @__PURE__ */ React13.createElement(
|
|
15254
15744
|
TouchableOpacity10,
|
|
15255
15745
|
{
|
|
15256
15746
|
style: [styles11.sendBtn, (!replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading) && styles11.sendBtnDisabled],
|
|
15257
15747
|
onPress: handleSend,
|
|
15258
15748
|
disabled: !replyText.trim() && replyImages.images.length === 0 || sending || replyImages.isUploading
|
|
15259
15749
|
},
|
|
15260
|
-
/* @__PURE__ */
|
|
15750
|
+
/* @__PURE__ */ React13.createElement(Text11, { style: styles11.sendBtnText }, sending ? "..." : "Send")
|
|
15261
15751
|
)));
|
|
15262
15752
|
}
|
|
15263
|
-
var styles11 =
|
|
15753
|
+
var styles11 = StyleSheet13.create({
|
|
15264
15754
|
container: { flex: 1 },
|
|
15265
15755
|
header: { flexDirection: "row", alignItems: "center", gap: 8, marginBottom: 16, paddingBottom: 12, borderBottomWidth: 1, borderBottomColor: colors.border },
|
|
15266
15756
|
headerIcon: { fontSize: 20 },
|
|
@@ -15292,8 +15782,8 @@ var styles11 = StyleSheet12.create({
|
|
|
15292
15782
|
});
|
|
15293
15783
|
|
|
15294
15784
|
// src/widget/screens/ComposeMessageScreen.tsx
|
|
15295
|
-
import
|
|
15296
|
-
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";
|
|
15297
15787
|
function ComposeMessageScreen({ nav }) {
|
|
15298
15788
|
const { createThread, uploadImage } = useBugBear();
|
|
15299
15789
|
const [subject, setSubject] = useState9("");
|
|
@@ -15314,8 +15804,8 @@ function ComposeMessageScreen({ nav }) {
|
|
|
15314
15804
|
nav.pop();
|
|
15315
15805
|
}
|
|
15316
15806
|
};
|
|
15317
|
-
return /* @__PURE__ */
|
|
15318
|
-
|
|
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,
|
|
15319
15809
|
{
|
|
15320
15810
|
style: styles12.subjectInput,
|
|
15321
15811
|
value: subject,
|
|
@@ -15324,8 +15814,8 @@ function ComposeMessageScreen({ nav }) {
|
|
|
15324
15814
|
placeholderTextColor: colors.textMuted,
|
|
15325
15815
|
maxLength: 100
|
|
15326
15816
|
}
|
|
15327
|
-
), /* @__PURE__ */
|
|
15328
|
-
|
|
15817
|
+
), /* @__PURE__ */ React14.createElement(Text12, { style: [shared.label, { marginTop: 16 }] }, "Message"), /* @__PURE__ */ React14.createElement(
|
|
15818
|
+
TextInput6,
|
|
15329
15819
|
{
|
|
15330
15820
|
style: styles12.messageInput,
|
|
15331
15821
|
value: message,
|
|
@@ -15337,7 +15827,7 @@ function ComposeMessageScreen({ nav }) {
|
|
|
15337
15827
|
textAlignVertical: "top",
|
|
15338
15828
|
maxLength: 2e3
|
|
15339
15829
|
}
|
|
15340
|
-
), /* @__PURE__ */
|
|
15830
|
+
), /* @__PURE__ */ React14.createElement(
|
|
15341
15831
|
ImagePickerButtons,
|
|
15342
15832
|
{
|
|
15343
15833
|
images: images.images,
|
|
@@ -15346,17 +15836,17 @@ function ComposeMessageScreen({ nav }) {
|
|
|
15346
15836
|
onPickCamera: images.pickFromCamera,
|
|
15347
15837
|
onRemove: images.removeImage
|
|
15348
15838
|
}
|
|
15349
|
-
), /* @__PURE__ */
|
|
15839
|
+
), /* @__PURE__ */ React14.createElement(
|
|
15350
15840
|
TouchableOpacity11,
|
|
15351
15841
|
{
|
|
15352
15842
|
style: [shared.primaryButton, (!subject.trim() || !message.trim() || sending || images.isUploading) && shared.primaryButtonDisabled, { marginTop: 20 }],
|
|
15353
15843
|
onPress: handleSend,
|
|
15354
15844
|
disabled: !subject.trim() || !message.trim() || sending || images.isUploading
|
|
15355
15845
|
},
|
|
15356
|
-
/* @__PURE__ */
|
|
15846
|
+
/* @__PURE__ */ React14.createElement(Text12, { style: shared.primaryButtonText }, images.isUploading ? "Uploading..." : sending ? "Sending..." : "Send Message")
|
|
15357
15847
|
)));
|
|
15358
15848
|
}
|
|
15359
|
-
var styles12 =
|
|
15849
|
+
var styles12 = StyleSheet14.create({
|
|
15360
15850
|
header: { marginBottom: 20 },
|
|
15361
15851
|
title: { fontSize: 20, fontWeight: "600", color: colors.textPrimary, marginBottom: 4 },
|
|
15362
15852
|
subtitle: { fontSize: 14, color: colors.textMuted },
|
|
@@ -15366,8 +15856,8 @@ var styles12 = StyleSheet13.create({
|
|
|
15366
15856
|
});
|
|
15367
15857
|
|
|
15368
15858
|
// src/widget/screens/ProfileScreen.tsx
|
|
15369
|
-
import
|
|
15370
|
-
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";
|
|
15371
15861
|
function ProfileScreen({ nav }) {
|
|
15372
15862
|
const { testerInfo, assignments, updateTesterProfile, refreshTesterInfo } = useBugBear();
|
|
15373
15863
|
const [editing, setEditing] = useState10(false);
|
|
@@ -15379,7 +15869,7 @@ function ProfileScreen({ nav }) {
|
|
|
15379
15869
|
const [saved, setSaved] = useState10(false);
|
|
15380
15870
|
const [showDetails, setShowDetails] = useState10(false);
|
|
15381
15871
|
const completedCount = assignments.filter((a) => a.status === "passed" || a.status === "failed").length;
|
|
15382
|
-
|
|
15872
|
+
useEffect9(() => {
|
|
15383
15873
|
if (testerInfo) {
|
|
15384
15874
|
setName(testerInfo.name);
|
|
15385
15875
|
setAdditionalEmails(testerInfo.additionalEmails || []);
|
|
@@ -15414,17 +15904,17 @@ function ProfileScreen({ nav }) {
|
|
|
15414
15904
|
}
|
|
15415
15905
|
};
|
|
15416
15906
|
if (saved) {
|
|
15417
|
-
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!"));
|
|
15418
15908
|
}
|
|
15419
15909
|
if (!testerInfo) {
|
|
15420
|
-
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"));
|
|
15421
15911
|
}
|
|
15422
15912
|
if (editing) {
|
|
15423
|
-
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: () => {
|
|
15424
15914
|
setEditing(false);
|
|
15425
15915
|
setNewEmailInput("");
|
|
15426
|
-
} }, /* @__PURE__ */
|
|
15427
|
-
|
|
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,
|
|
15428
15918
|
{
|
|
15429
15919
|
style: [styles13.input, { flex: 1, marginRight: 8 }],
|
|
15430
15920
|
value: newEmailInput,
|
|
@@ -15434,26 +15924,26 @@ function ProfileScreen({ nav }) {
|
|
|
15434
15924
|
keyboardType: "email-address",
|
|
15435
15925
|
autoCapitalize: "none"
|
|
15436
15926
|
}
|
|
15437
|
-
), /* @__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(
|
|
15438
15928
|
TouchableOpacity12,
|
|
15439
15929
|
{
|
|
15440
15930
|
key,
|
|
15441
15931
|
style: [styles13.platformBtn, platforms.includes(key) && styles13.platformBtnActive],
|
|
15442
15932
|
onPress: () => setPlatforms((prev) => prev.includes(key) ? prev.filter((p) => p !== key) : [...prev, key])
|
|
15443
15933
|
},
|
|
15444
|
-
/* @__PURE__ */
|
|
15445
|
-
)))), /* @__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")));
|
|
15446
15936
|
}
|
|
15447
|
-
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(
|
|
15448
15938
|
TouchableOpacity12,
|
|
15449
15939
|
{
|
|
15450
15940
|
style: [shared.primaryButton, { marginTop: 20 }],
|
|
15451
15941
|
onPress: () => setEditing(true)
|
|
15452
15942
|
},
|
|
15453
|
-
/* @__PURE__ */
|
|
15943
|
+
/* @__PURE__ */ React15.createElement(Text13, { style: shared.primaryButtonText }, "Edit Profile")
|
|
15454
15944
|
));
|
|
15455
15945
|
}
|
|
15456
|
-
var styles13 =
|
|
15946
|
+
var styles13 = StyleSheet15.create({
|
|
15457
15947
|
profileCard: { alignItems: "center", backgroundColor: colors.card, borderRadius: 16, padding: 24, marginBottom: 16 },
|
|
15458
15948
|
avatar: { width: 64, height: 64, borderRadius: 32, backgroundColor: colors.blue, justifyContent: "center", alignItems: "center", marginBottom: 12 },
|
|
15459
15949
|
avatarText: { fontSize: 28, fontWeight: "700", color: "#fff" },
|
|
@@ -15494,8 +15984,8 @@ var styles13 = StyleSheet14.create({
|
|
|
15494
15984
|
});
|
|
15495
15985
|
|
|
15496
15986
|
// src/widget/screens/IssueListScreen.tsx
|
|
15497
|
-
import
|
|
15498
|
-
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";
|
|
15499
15989
|
var CATEGORY_CONFIG = {
|
|
15500
15990
|
open: { label: "Open Issues", accent: "#f97316", emptyIcon: "\u2705", emptyText: "No open issues" },
|
|
15501
15991
|
done: { label: "Done", accent: "#22c55e", emptyIcon: "\u{1F389}", emptyText: "No completed issues yet" },
|
|
@@ -15512,7 +16002,7 @@ function IssueListScreen({ nav, category }) {
|
|
|
15512
16002
|
const [issues, setIssues] = useState11([]);
|
|
15513
16003
|
const [loading, setLoading] = useState11(true);
|
|
15514
16004
|
const config = CATEGORY_CONFIG[category];
|
|
15515
|
-
|
|
16005
|
+
useEffect10(() => {
|
|
15516
16006
|
let cancelled = false;
|
|
15517
16007
|
setLoading(true);
|
|
15518
16008
|
(async () => {
|
|
@@ -15538,12 +16028,12 @@ function IssueListScreen({ nav, category }) {
|
|
|
15538
16028
|
};
|
|
15539
16029
|
}, [client, category]);
|
|
15540
16030
|
if (loading) {
|
|
15541
|
-
return /* @__PURE__ */
|
|
16031
|
+
return /* @__PURE__ */ React16.createElement(IssueListScreenSkeleton, null);
|
|
15542
16032
|
}
|
|
15543
16033
|
if (issues.length === 0) {
|
|
15544
|
-
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));
|
|
15545
16035
|
}
|
|
15546
|
-
return /* @__PURE__ */
|
|
16036
|
+
return /* @__PURE__ */ React16.createElement(View15, null, issues.map((issue) => /* @__PURE__ */ React16.createElement(
|
|
15547
16037
|
TouchableOpacity13,
|
|
15548
16038
|
{
|
|
15549
16039
|
key: issue.id,
|
|
@@ -15551,13 +16041,13 @@ function IssueListScreen({ nav, category }) {
|
|
|
15551
16041
|
onPress: () => nav.push({ name: "ISSUE_DETAIL", issue }),
|
|
15552
16042
|
activeOpacity: 0.7
|
|
15553
16043
|
},
|
|
15554
|
-
/* @__PURE__ */
|
|
15555
|
-
/* @__PURE__ */
|
|
15556
|
-
category === "done" && issue.verifiedByName && /* @__PURE__ */
|
|
15557
|
-
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))
|
|
15558
16048
|
)));
|
|
15559
16049
|
}
|
|
15560
|
-
var styles14 =
|
|
16050
|
+
var styles14 = StyleSheet16.create({
|
|
15561
16051
|
emptyContainer: {
|
|
15562
16052
|
alignItems: "center",
|
|
15563
16053
|
paddingVertical: 40
|
|
@@ -15648,8 +16138,8 @@ var styles14 = StyleSheet15.create({
|
|
|
15648
16138
|
});
|
|
15649
16139
|
|
|
15650
16140
|
// src/widget/screens/IssueDetailScreen.tsx
|
|
15651
|
-
import
|
|
15652
|
-
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";
|
|
15653
16143
|
var STATUS_LABELS = {
|
|
15654
16144
|
new: { label: "New", bg: "#1e3a5f", color: "#60a5fa" },
|
|
15655
16145
|
triaging: { label: "Triaging", bg: "#1e3a5f", color: "#60a5fa" },
|
|
@@ -15673,9 +16163,9 @@ var SEVERITY_CONFIG = {
|
|
|
15673
16163
|
function IssueDetailScreen({ nav, issue }) {
|
|
15674
16164
|
const statusConfig = STATUS_LABELS[issue.status] || { label: issue.status, bg: "#27272a", color: "#a1a1aa" };
|
|
15675
16165
|
const severityConfig = issue.severity ? SEVERITY_CONFIG[issue.severity] : null;
|
|
15676
|
-
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))));
|
|
15677
16167
|
}
|
|
15678
|
-
var styles15 =
|
|
16168
|
+
var styles15 = StyleSheet17.create({
|
|
15679
16169
|
badgeRow: {
|
|
15680
16170
|
flexDirection: "row",
|
|
15681
16171
|
gap: 8,
|
|
@@ -15831,9 +16321,9 @@ function BugBearButton({
|
|
|
15831
16321
|
return { x, y };
|
|
15832
16322
|
};
|
|
15833
16323
|
const initialPos = getInitialPosition();
|
|
15834
|
-
const pan =
|
|
15835
|
-
const isDragging =
|
|
15836
|
-
const panResponder =
|
|
16324
|
+
const pan = useRef4(new Animated2.ValueXY(initialPos)).current;
|
|
16325
|
+
const isDragging = useRef4(false);
|
|
16326
|
+
const panResponder = useRef4(
|
|
15837
16327
|
PanResponder.create({
|
|
15838
16328
|
onStartShouldSetPanResponder: () => draggable,
|
|
15839
16329
|
onMoveShouldSetPanResponder: (_, gs) => draggable && (Math.abs(gs.dx) > 5 || Math.abs(gs.dy) > 5),
|
|
@@ -15849,7 +16339,7 @@ function BugBearButton({
|
|
|
15849
16339
|
if (Math.abs(gs.dx) > 5 || Math.abs(gs.dy) > 5) {
|
|
15850
16340
|
isDragging.current = true;
|
|
15851
16341
|
}
|
|
15852
|
-
|
|
16342
|
+
Animated2.event(
|
|
15853
16343
|
[null, { dx: pan.x, dy: pan.y }],
|
|
15854
16344
|
{ useNativeDriver: false }
|
|
15855
16345
|
)(_, gs);
|
|
@@ -15862,7 +16352,7 @@ function BugBearButton({
|
|
|
15862
16352
|
const margin = 16;
|
|
15863
16353
|
const snapX = currentX < screenWidth / 2 ? margin : screenWidth - buttonSize - margin;
|
|
15864
16354
|
const snapY = Math.max(minY, Math.min(currentY, screenHeight - maxYOffset));
|
|
15865
|
-
|
|
16355
|
+
Animated2.spring(pan, {
|
|
15866
16356
|
toValue: { x: snapX, y: snapY },
|
|
15867
16357
|
useNativeDriver: false,
|
|
15868
16358
|
friction: 7,
|
|
@@ -15935,50 +16425,50 @@ function BugBearButton({
|
|
|
15935
16425
|
const renderScreen = () => {
|
|
15936
16426
|
switch (currentScreen.name) {
|
|
15937
16427
|
case "HOME":
|
|
15938
|
-
return /* @__PURE__ */
|
|
16428
|
+
return /* @__PURE__ */ React18.createElement(HomeScreen, { nav });
|
|
15939
16429
|
case "TEST_DETAIL":
|
|
15940
|
-
return /* @__PURE__ */
|
|
16430
|
+
return /* @__PURE__ */ React18.createElement(TestDetailScreen, { testId: currentScreen.testId, nav });
|
|
15941
16431
|
case "TEST_LIST":
|
|
15942
|
-
return /* @__PURE__ */
|
|
16432
|
+
return /* @__PURE__ */ React18.createElement(TestListScreen, { nav });
|
|
15943
16433
|
case "TEST_FEEDBACK":
|
|
15944
|
-
return /* @__PURE__ */
|
|
16434
|
+
return /* @__PURE__ */ React18.createElement(TestFeedbackScreen, { status: currentScreen.status, assignmentId: currentScreen.assignmentId, nav });
|
|
15945
16435
|
case "REPORT":
|
|
15946
|
-
return /* @__PURE__ */
|
|
16436
|
+
return /* @__PURE__ */ React18.createElement(ReportScreen, { nav, prefill: currentScreen.prefill });
|
|
15947
16437
|
case "REPORT_SUCCESS":
|
|
15948
|
-
return /* @__PURE__ */
|
|
16438
|
+
return /* @__PURE__ */ React18.createElement(ReportSuccessScreen, { nav });
|
|
15949
16439
|
case "MESSAGE_LIST":
|
|
15950
|
-
return /* @__PURE__ */
|
|
16440
|
+
return /* @__PURE__ */ React18.createElement(MessageListScreen, { nav });
|
|
15951
16441
|
case "THREAD_DETAIL":
|
|
15952
|
-
return /* @__PURE__ */
|
|
16442
|
+
return /* @__PURE__ */ React18.createElement(ThreadDetailScreen, { thread: currentScreen.thread, nav });
|
|
15953
16443
|
case "COMPOSE_MESSAGE":
|
|
15954
|
-
return /* @__PURE__ */
|
|
16444
|
+
return /* @__PURE__ */ React18.createElement(ComposeMessageScreen, { nav });
|
|
15955
16445
|
case "ISSUE_LIST":
|
|
15956
|
-
return /* @__PURE__ */
|
|
16446
|
+
return /* @__PURE__ */ React18.createElement(IssueListScreen, { nav, category: currentScreen.category });
|
|
15957
16447
|
case "ISSUE_DETAIL":
|
|
15958
|
-
return /* @__PURE__ */
|
|
16448
|
+
return /* @__PURE__ */ React18.createElement(IssueDetailScreen, { nav, issue: currentScreen.issue });
|
|
15959
16449
|
case "PROFILE":
|
|
15960
|
-
return /* @__PURE__ */
|
|
16450
|
+
return /* @__PURE__ */ React18.createElement(ProfileScreen, { nav });
|
|
15961
16451
|
default:
|
|
15962
|
-
return /* @__PURE__ */
|
|
16452
|
+
return /* @__PURE__ */ React18.createElement(HomeScreen, { nav });
|
|
15963
16453
|
}
|
|
15964
16454
|
};
|
|
15965
|
-
return /* @__PURE__ */
|
|
15966
|
-
|
|
16455
|
+
return /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement(
|
|
16456
|
+
Animated2.View,
|
|
15967
16457
|
{
|
|
15968
16458
|
style: [styles16.fabContainer, { transform: pan.getTranslateTransform() }, buttonStyle],
|
|
15969
16459
|
...panResponder.panHandlers
|
|
15970
16460
|
},
|
|
15971
|
-
/* @__PURE__ */
|
|
16461
|
+
/* @__PURE__ */ React18.createElement(
|
|
15972
16462
|
TouchableOpacity15,
|
|
15973
16463
|
{
|
|
15974
16464
|
style: styles16.fab,
|
|
15975
16465
|
onPress: () => setModalVisible(true),
|
|
15976
16466
|
activeOpacity: draggable ? 1 : 0.7
|
|
15977
16467
|
},
|
|
15978
|
-
/* @__PURE__ */
|
|
15979
|
-
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))
|
|
15980
16470
|
)
|
|
15981
|
-
), /* @__PURE__ */
|
|
16471
|
+
), /* @__PURE__ */ React18.createElement(
|
|
15982
16472
|
Modal3,
|
|
15983
16473
|
{
|
|
15984
16474
|
visible: modalVisible,
|
|
@@ -15986,13 +16476,13 @@ function BugBearButton({
|
|
|
15986
16476
|
transparent: true,
|
|
15987
16477
|
onRequestClose: handleClose
|
|
15988
16478
|
},
|
|
15989
|
-
/* @__PURE__ */
|
|
16479
|
+
/* @__PURE__ */ React18.createElement(
|
|
15990
16480
|
KeyboardAvoidingView,
|
|
15991
16481
|
{
|
|
15992
16482
|
behavior: Platform4.OS === "ios" ? "padding" : "height",
|
|
15993
16483
|
style: styles16.modalOverlay
|
|
15994
16484
|
},
|
|
15995
|
-
/* @__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(
|
|
15996
16486
|
ScrollView3,
|
|
15997
16487
|
{
|
|
15998
16488
|
style: styles16.content,
|
|
@@ -16000,12 +16490,12 @@ function BugBearButton({
|
|
|
16000
16490
|
keyboardShouldPersistTaps: "handled",
|
|
16001
16491
|
showsVerticalScrollIndicator: false
|
|
16002
16492
|
},
|
|
16003
|
-
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()
|
|
16004
16494
|
))
|
|
16005
16495
|
)
|
|
16006
16496
|
));
|
|
16007
16497
|
}
|
|
16008
|
-
var styles16 =
|
|
16498
|
+
var styles16 = StyleSheet18.create({
|
|
16009
16499
|
// FAB
|
|
16010
16500
|
fabContainer: {
|
|
16011
16501
|
position: "absolute",
|
|
@@ -16147,8 +16637,8 @@ var styles16 = StyleSheet17.create({
|
|
|
16147
16637
|
});
|
|
16148
16638
|
|
|
16149
16639
|
// src/BugBearErrorBoundary.tsx
|
|
16150
|
-
import
|
|
16151
|
-
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";
|
|
16152
16642
|
var BugBearErrorBoundary = class extends Component {
|
|
16153
16643
|
constructor(props) {
|
|
16154
16644
|
super(props);
|
|
@@ -16193,7 +16683,7 @@ var BugBearErrorBoundary = class extends Component {
|
|
|
16193
16683
|
if (fallback) {
|
|
16194
16684
|
return fallback;
|
|
16195
16685
|
}
|
|
16196
|
-
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"));
|
|
16197
16687
|
}
|
|
16198
16688
|
return children;
|
|
16199
16689
|
}
|
|
@@ -16204,7 +16694,7 @@ function useErrorContext() {
|
|
|
16204
16694
|
getEnhancedContext: () => contextCapture.getEnhancedContext()
|
|
16205
16695
|
};
|
|
16206
16696
|
}
|
|
16207
|
-
var styles17 =
|
|
16697
|
+
var styles17 = StyleSheet19.create({
|
|
16208
16698
|
container: {
|
|
16209
16699
|
padding: 20,
|
|
16210
16700
|
margin: 20,
|