@bbearai/react-native 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -27,6 +27,17 @@ interface BugBearContextValue {
27
27
  sendMessage: (threadId: string, content: string) => Promise<boolean>;
28
28
  /** Mark a thread as read */
29
29
  markAsRead: (threadId: string) => Promise<void>;
30
+ /** Create a new thread */
31
+ createThread: (options: {
32
+ subject: string;
33
+ message: string;
34
+ }) => Promise<{
35
+ success: boolean;
36
+ threadId?: string;
37
+ error?: string;
38
+ }>;
39
+ /** Re-check tester status (call after auth state changes) */
40
+ refreshTesterStatus: () => Promise<void>;
30
41
  }
31
42
  declare function useBugBear(): BugBearContextValue;
32
43
  interface BugBearProviderProps {
@@ -34,8 +45,14 @@ interface BugBearProviderProps {
34
45
  children: ReactNode;
35
46
  /** App version string */
36
47
  appVersion?: string;
48
+ /**
49
+ * Set to false to delay initialization until auth is ready.
50
+ * When enabled changes from false to true, BugBear will initialize.
51
+ * @default true
52
+ */
53
+ enabled?: boolean;
37
54
  }
38
- declare function BugBearProvider({ config, children, appVersion }: BugBearProviderProps): React.JSX.Element;
55
+ declare function BugBearProvider({ config, children, appVersion, enabled }: BugBearProviderProps): React.JSX.Element;
39
56
 
40
57
  interface BugBearButtonProps {
41
58
  /** Get current app context */
package/dist/index.d.ts CHANGED
@@ -27,6 +27,17 @@ interface BugBearContextValue {
27
27
  sendMessage: (threadId: string, content: string) => Promise<boolean>;
28
28
  /** Mark a thread as read */
29
29
  markAsRead: (threadId: string) => Promise<void>;
30
+ /** Create a new thread */
31
+ createThread: (options: {
32
+ subject: string;
33
+ message: string;
34
+ }) => Promise<{
35
+ success: boolean;
36
+ threadId?: string;
37
+ error?: string;
38
+ }>;
39
+ /** Re-check tester status (call after auth state changes) */
40
+ refreshTesterStatus: () => Promise<void>;
30
41
  }
31
42
  declare function useBugBear(): BugBearContextValue;
32
43
  interface BugBearProviderProps {
@@ -34,8 +45,14 @@ interface BugBearProviderProps {
34
45
  children: ReactNode;
35
46
  /** App version string */
36
47
  appVersion?: string;
48
+ /**
49
+ * Set to false to delay initialization until auth is ready.
50
+ * When enabled changes from false to true, BugBear will initialize.
51
+ * @default true
52
+ */
53
+ enabled?: boolean;
37
54
  }
38
- declare function BugBearProvider({ config, children, appVersion }: BugBearProviderProps): React.JSX.Element;
55
+ declare function BugBearProvider({ config, children, appVersion, enabled }: BugBearProviderProps): React.JSX.Element;
39
56
 
40
57
  interface BugBearButtonProps {
41
58
  /** Get current app context */
package/dist/index.js CHANGED
@@ -60,12 +60,15 @@ var BugBearContext = (0, import_react.createContext)({
60
60
  getThreadMessages: async () => [],
61
61
  sendMessage: async () => false,
62
62
  markAsRead: async () => {
63
+ },
64
+ createThread: async () => ({ success: false }),
65
+ refreshTesterStatus: async () => {
63
66
  }
64
67
  });
65
68
  function useBugBear() {
66
69
  return (0, import_react.useContext)(BugBearContext);
67
70
  }
68
- function BugBearProvider({ config, children, appVersion }) {
71
+ function BugBearProvider({ config, children, appVersion, enabled = true }) {
69
72
  const [client] = (0, import_react.useState)(() => (0, import_core.createBugBear)(config));
70
73
  const [isTester, setIsTester] = (0, import_react.useState)(false);
71
74
  const [isQAEnabled, setIsQAEnabled] = (0, import_react.useState)(false);
@@ -74,7 +77,9 @@ function BugBearProvider({ config, children, appVersion }) {
74
77
  const [isLoading, setIsLoading] = (0, import_react.useState)(true);
75
78
  const [threads, setThreads] = (0, import_react.useState)([]);
76
79
  const [unreadCount, setUnreadCount] = (0, import_react.useState)(0);
77
- const getDeviceInfo = () => {
80
+ const [initCount, setInitCount] = (0, import_react.useState)(0);
81
+ const hasInitialized = (0, import_react.useRef)(false);
82
+ const getDeviceInfo = (0, import_react.useCallback)(() => {
78
83
  const { width, height } = import_react_native.Dimensions.get("window");
79
84
  return {
80
85
  platform: import_react_native.Platform.OS,
@@ -82,56 +87,74 @@ function BugBearProvider({ config, children, appVersion }) {
82
87
  appVersion,
83
88
  screenSize: { width, height }
84
89
  };
85
- };
86
- const refreshAssignments = async () => {
90
+ }, [appVersion]);
91
+ const refreshAssignments = (0, import_react.useCallback)(async () => {
87
92
  const newAssignments = await client.getAssignedTests();
88
93
  setAssignments(newAssignments);
89
- };
90
- const refreshThreads = async () => {
94
+ }, [client]);
95
+ const refreshThreads = (0, import_react.useCallback)(async () => {
91
96
  const newThreads = await client.getThreadsForTester();
92
97
  setThreads(newThreads);
93
98
  const totalUnread = newThreads.reduce((sum, t) => sum + t.unreadCount, 0);
94
99
  setUnreadCount(totalUnread);
95
- };
96
- const getThreadMessages = async (threadId) => {
100
+ }, [client]);
101
+ const getThreadMessages = (0, import_react.useCallback)(async (threadId) => {
97
102
  return client.getThreadMessages(threadId);
98
- };
99
- const sendMessage = async (threadId, content) => {
103
+ }, [client]);
104
+ const sendMessage = (0, import_react.useCallback)(async (threadId, content) => {
100
105
  const success = await client.sendMessage(threadId, content);
101
106
  if (success) {
102
107
  await refreshThreads();
103
108
  }
104
109
  return success;
105
- };
106
- const markAsRead = async (threadId) => {
110
+ }, [client, refreshThreads]);
111
+ const markAsRead = (0, import_react.useCallback)(async (threadId) => {
107
112
  await client.markThreadAsRead(threadId);
108
113
  await refreshThreads();
109
- };
110
- (0, import_react.useEffect)(() => {
111
- const init = async () => {
112
- setIsLoading(true);
113
- try {
114
- const [qaEnabled, info] = await Promise.all([
115
- client.isQAEnabled(),
116
- client.getTesterInfo()
114
+ }, [client, refreshThreads]);
115
+ const createThread = (0, import_react.useCallback)(async (options) => {
116
+ const result = await client.createThread(options);
117
+ if (result.success) {
118
+ await refreshThreads();
119
+ }
120
+ return result;
121
+ }, [client, refreshThreads]);
122
+ const initializeBugBear = (0, import_react.useCallback)(async () => {
123
+ setIsLoading(true);
124
+ try {
125
+ const [qaEnabled, info] = await Promise.all([
126
+ client.isQAEnabled(),
127
+ client.getTesterInfo()
128
+ ]);
129
+ setIsQAEnabled(qaEnabled);
130
+ setTesterInfo(info);
131
+ setIsTester(!!info);
132
+ if (info && qaEnabled) {
133
+ await Promise.all([
134
+ refreshAssignments(),
135
+ refreshThreads()
117
136
  ]);
118
- setIsQAEnabled(qaEnabled);
119
- setTesterInfo(info);
120
- setIsTester(!!info);
121
- if (info && qaEnabled) {
122
- await Promise.all([
123
- refreshAssignments(),
124
- refreshThreads()
125
- ]);
126
- }
127
- } catch (err) {
128
- console.error("BugBear: Init error", err);
129
- } finally {
130
- setIsLoading(false);
131
137
  }
132
- };
133
- init();
134
- }, [client]);
138
+ } catch (err) {
139
+ console.error("BugBear: Init error", err);
140
+ } finally {
141
+ setIsLoading(false);
142
+ }
143
+ }, [client, refreshAssignments, refreshThreads]);
144
+ const refreshTesterStatus = (0, import_react.useCallback)(async () => {
145
+ await initializeBugBear();
146
+ }, [initializeBugBear]);
147
+ (0, import_react.useEffect)(() => {
148
+ if (enabled && !hasInitialized.current) {
149
+ hasInitialized.current = true;
150
+ initializeBugBear();
151
+ }
152
+ }, [enabled, initializeBugBear]);
153
+ (0, import_react.useEffect)(() => {
154
+ if (initCount > 0) {
155
+ initializeBugBear();
156
+ }
157
+ }, [initCount, initializeBugBear]);
135
158
  const currentAssignment = assignments.find(
136
159
  (a) => a.status === "in_progress"
137
160
  ) || assignments[0] || null;
@@ -156,7 +179,9 @@ function BugBearProvider({ config, children, appVersion }) {
156
179
  refreshThreads,
157
180
  getThreadMessages,
158
181
  sendMessage,
159
- markAsRead
182
+ markAsRead,
183
+ createThread,
184
+ refreshTesterStatus
160
185
  }
161
186
  },
162
187
  children
@@ -198,7 +223,8 @@ function BugBearButton({
198
223
  refreshThreads,
199
224
  getThreadMessages,
200
225
  sendMessage,
201
- markAsRead
226
+ markAsRead,
227
+ createThread
202
228
  } = useBugBear();
203
229
  const [modalVisible, setModalVisible] = (0, import_react2.useState)(false);
204
230
  const [activeTab, setActiveTab] = (0, import_react2.useState)("tests");
@@ -209,6 +235,9 @@ function BugBearButton({
209
235
  const [replyText, setReplyText] = (0, import_react2.useState)("");
210
236
  const [sendingReply, setSendingReply] = (0, import_react2.useState)(false);
211
237
  const [loadingMessages, setLoadingMessages] = (0, import_react2.useState)(false);
238
+ const [composeSubject, setComposeSubject] = (0, import_react2.useState)("");
239
+ const [composeMessage, setComposeMessage] = (0, import_react2.useState)("");
240
+ const [sendingNewMessage, setSendingNewMessage] = (0, import_react2.useState)(false);
212
241
  const getInitialPosition = () => {
213
242
  const buttonSize = 56;
214
243
  const margin = 16;
@@ -371,6 +400,27 @@ function BugBearButton({
371
400
  setSelectedThread(null);
372
401
  setThreadMessages([]);
373
402
  setReplyText("");
403
+ setComposeSubject("");
404
+ setComposeMessage("");
405
+ };
406
+ const handleStartNewMessage = () => {
407
+ setMessageView("compose");
408
+ setComposeSubject("");
409
+ setComposeMessage("");
410
+ };
411
+ const handleSendNewMessage = async () => {
412
+ if (!composeSubject.trim() || !composeMessage.trim()) return;
413
+ setSendingNewMessage(true);
414
+ const result = await createThread({
415
+ subject: composeSubject.trim(),
416
+ message: composeMessage.trim()
417
+ });
418
+ if (result.success) {
419
+ setComposeSubject("");
420
+ setComposeMessage("");
421
+ setMessageView("list");
422
+ }
423
+ setSendingNewMessage(false);
374
424
  };
375
425
  const formatRelativeTime = (dateString) => {
376
426
  const date = new Date(dateString);
@@ -622,9 +672,46 @@ function BugBearButton({
622
672
  },
623
673
  /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.passButtonText }, submitting ? "..." : "\u2713 Pass")
624
674
  )))
625
- ) : null), activeTab === "messages" && /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, null, messageView === "list" ? (
675
+ ) : null), activeTab === "messages" && /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, null, messageView === "compose" ? (
676
+ /* Compose New Message View */
677
+ /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, null, /* @__PURE__ */ import_react2.default.createElement(import_react_native2.TouchableOpacity, { onPress: handleBackToThreadList, style: styles.backButton }, /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.backButtonText }, "\u2190 Back to Messages")), /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, { style: styles.composeHeader }, /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.composeTitle }, "New Message"), /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.composeSubtitle }, "Send a message to the QA team")), /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, { style: styles.composeForm }, /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.label }, "Subject"), /* @__PURE__ */ import_react2.default.createElement(
678
+ import_react_native2.TextInput,
679
+ {
680
+ style: styles.composeSubjectInput,
681
+ value: composeSubject,
682
+ onChangeText: setComposeSubject,
683
+ placeholder: "What's this about?",
684
+ placeholderTextColor: "#9CA3AF",
685
+ maxLength: 100
686
+ }
687
+ ), /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: [styles.label, { marginTop: 16 }] }, "Message"), /* @__PURE__ */ import_react2.default.createElement(
688
+ import_react_native2.TextInput,
689
+ {
690
+ style: styles.composeMessageInput,
691
+ value: composeMessage,
692
+ onChangeText: setComposeMessage,
693
+ placeholder: "Write your message...",
694
+ placeholderTextColor: "#9CA3AF",
695
+ multiline: true,
696
+ numberOfLines: 6,
697
+ textAlignVertical: "top",
698
+ maxLength: 2e3
699
+ }
700
+ ), /* @__PURE__ */ import_react2.default.createElement(
701
+ import_react_native2.TouchableOpacity,
702
+ {
703
+ style: [
704
+ styles.composeSendButton,
705
+ (!composeSubject.trim() || !composeMessage.trim() || sendingNewMessage) && styles.composeSendButtonDisabled
706
+ ],
707
+ onPress: handleSendNewMessage,
708
+ disabled: !composeSubject.trim() || !composeMessage.trim() || sendingNewMessage
709
+ },
710
+ /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.composeSendButtonText }, sendingNewMessage ? "Sending..." : "Send Message")
711
+ )))
712
+ ) : messageView === "list" ? (
626
713
  /* Thread List View */
627
- /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, null, threads.length === 0 ? /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, { style: styles.emptyState }, /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.emptyTitle }, "No messages yet"), /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.emptySubtitle }, "Messages from admins will appear here")) : /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, null, threads.map((thread) => /* @__PURE__ */ import_react2.default.createElement(
714
+ /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, null, /* @__PURE__ */ import_react2.default.createElement(import_react_native2.TouchableOpacity, { onPress: handleStartNewMessage, style: styles.newMessageButton }, /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.newMessageButtonIcon }, "\u2709\uFE0F"), /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.newMessageButtonText }, "New Message")), threads.length === 0 ? /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, { style: styles.emptyState }, /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.emptyTitle }, "No messages yet"), /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, null, threads.map((thread) => /* @__PURE__ */ import_react2.default.createElement(
628
715
  import_react_native2.TouchableOpacity,
629
716
  {
630
717
  key: thread.id,
@@ -1654,6 +1741,81 @@ var styles = import_react_native2.StyleSheet.create({
1654
1741
  fontSize: 14,
1655
1742
  fontWeight: "600",
1656
1743
  color: "#fff"
1744
+ },
1745
+ // New message button styles
1746
+ newMessageButton: {
1747
+ flexDirection: "row",
1748
+ alignItems: "center",
1749
+ justifyContent: "center",
1750
+ backgroundColor: "#7C3AED",
1751
+ paddingVertical: 12,
1752
+ paddingHorizontal: 20,
1753
+ borderRadius: 12,
1754
+ marginBottom: 16
1755
+ },
1756
+ newMessageButtonIcon: {
1757
+ fontSize: 16,
1758
+ marginRight: 8
1759
+ },
1760
+ newMessageButtonText: {
1761
+ fontSize: 15,
1762
+ fontWeight: "600",
1763
+ color: "#fff"
1764
+ },
1765
+ // Compose view styles
1766
+ composeHeader: {
1767
+ marginBottom: 20
1768
+ },
1769
+ composeTitle: {
1770
+ fontSize: 20,
1771
+ fontWeight: "600",
1772
+ color: "#111827",
1773
+ marginBottom: 4
1774
+ },
1775
+ composeSubtitle: {
1776
+ fontSize: 14,
1777
+ color: "#6B7280"
1778
+ },
1779
+ composeForm: {
1780
+ backgroundColor: "#F9FAFB",
1781
+ borderRadius: 12,
1782
+ padding: 16
1783
+ },
1784
+ composeSubjectInput: {
1785
+ backgroundColor: "#fff",
1786
+ borderWidth: 1,
1787
+ borderColor: "#E5E7EB",
1788
+ borderRadius: 8,
1789
+ paddingHorizontal: 12,
1790
+ paddingVertical: 10,
1791
+ fontSize: 15,
1792
+ color: "#111827"
1793
+ },
1794
+ composeMessageInput: {
1795
+ backgroundColor: "#fff",
1796
+ borderWidth: 1,
1797
+ borderColor: "#E5E7EB",
1798
+ borderRadius: 8,
1799
+ paddingHorizontal: 12,
1800
+ paddingVertical: 10,
1801
+ fontSize: 15,
1802
+ color: "#111827",
1803
+ minHeight: 120
1804
+ },
1805
+ composeSendButton: {
1806
+ backgroundColor: "#7C3AED",
1807
+ paddingVertical: 14,
1808
+ borderRadius: 12,
1809
+ alignItems: "center",
1810
+ marginTop: 20
1811
+ },
1812
+ composeSendButtonDisabled: {
1813
+ backgroundColor: "#C4B5FD"
1814
+ },
1815
+ composeSendButtonText: {
1816
+ fontSize: 16,
1817
+ fontWeight: "600",
1818
+ color: "#fff"
1657
1819
  }
1658
1820
  });
1659
1821
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/BugBearProvider.tsx
2
- import React, { createContext, useContext, useEffect, useState } from "react";
2
+ import React, { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
3
3
  import {
4
4
  createBugBear
5
5
  } from "@bbearai/core";
@@ -24,12 +24,15 @@ var BugBearContext = createContext({
24
24
  getThreadMessages: async () => [],
25
25
  sendMessage: async () => false,
26
26
  markAsRead: async () => {
27
+ },
28
+ createThread: async () => ({ success: false }),
29
+ refreshTesterStatus: async () => {
27
30
  }
28
31
  });
29
32
  function useBugBear() {
30
33
  return useContext(BugBearContext);
31
34
  }
32
- function BugBearProvider({ config, children, appVersion }) {
35
+ function BugBearProvider({ config, children, appVersion, enabled = true }) {
33
36
  const [client] = useState(() => createBugBear(config));
34
37
  const [isTester, setIsTester] = useState(false);
35
38
  const [isQAEnabled, setIsQAEnabled] = useState(false);
@@ -38,7 +41,9 @@ function BugBearProvider({ config, children, appVersion }) {
38
41
  const [isLoading, setIsLoading] = useState(true);
39
42
  const [threads, setThreads] = useState([]);
40
43
  const [unreadCount, setUnreadCount] = useState(0);
41
- const getDeviceInfo = () => {
44
+ const [initCount, setInitCount] = useState(0);
45
+ const hasInitialized = useRef(false);
46
+ const getDeviceInfo = useCallback(() => {
42
47
  const { width, height } = Dimensions.get("window");
43
48
  return {
44
49
  platform: Platform.OS,
@@ -46,56 +51,74 @@ function BugBearProvider({ config, children, appVersion }) {
46
51
  appVersion,
47
52
  screenSize: { width, height }
48
53
  };
49
- };
50
- const refreshAssignments = async () => {
54
+ }, [appVersion]);
55
+ const refreshAssignments = useCallback(async () => {
51
56
  const newAssignments = await client.getAssignedTests();
52
57
  setAssignments(newAssignments);
53
- };
54
- const refreshThreads = async () => {
58
+ }, [client]);
59
+ const refreshThreads = useCallback(async () => {
55
60
  const newThreads = await client.getThreadsForTester();
56
61
  setThreads(newThreads);
57
62
  const totalUnread = newThreads.reduce((sum, t) => sum + t.unreadCount, 0);
58
63
  setUnreadCount(totalUnread);
59
- };
60
- const getThreadMessages = async (threadId) => {
64
+ }, [client]);
65
+ const getThreadMessages = useCallback(async (threadId) => {
61
66
  return client.getThreadMessages(threadId);
62
- };
63
- const sendMessage = async (threadId, content) => {
67
+ }, [client]);
68
+ const sendMessage = useCallback(async (threadId, content) => {
64
69
  const success = await client.sendMessage(threadId, content);
65
70
  if (success) {
66
71
  await refreshThreads();
67
72
  }
68
73
  return success;
69
- };
70
- const markAsRead = async (threadId) => {
74
+ }, [client, refreshThreads]);
75
+ const markAsRead = useCallback(async (threadId) => {
71
76
  await client.markThreadAsRead(threadId);
72
77
  await refreshThreads();
73
- };
74
- useEffect(() => {
75
- const init = async () => {
76
- setIsLoading(true);
77
- try {
78
- const [qaEnabled, info] = await Promise.all([
79
- client.isQAEnabled(),
80
- client.getTesterInfo()
78
+ }, [client, refreshThreads]);
79
+ const createThread = useCallback(async (options) => {
80
+ const result = await client.createThread(options);
81
+ if (result.success) {
82
+ await refreshThreads();
83
+ }
84
+ return result;
85
+ }, [client, refreshThreads]);
86
+ const initializeBugBear = useCallback(async () => {
87
+ setIsLoading(true);
88
+ try {
89
+ const [qaEnabled, info] = await Promise.all([
90
+ client.isQAEnabled(),
91
+ client.getTesterInfo()
92
+ ]);
93
+ setIsQAEnabled(qaEnabled);
94
+ setTesterInfo(info);
95
+ setIsTester(!!info);
96
+ if (info && qaEnabled) {
97
+ await Promise.all([
98
+ refreshAssignments(),
99
+ refreshThreads()
81
100
  ]);
82
- setIsQAEnabled(qaEnabled);
83
- setTesterInfo(info);
84
- setIsTester(!!info);
85
- if (info && qaEnabled) {
86
- await Promise.all([
87
- refreshAssignments(),
88
- refreshThreads()
89
- ]);
90
- }
91
- } catch (err) {
92
- console.error("BugBear: Init error", err);
93
- } finally {
94
- setIsLoading(false);
95
101
  }
96
- };
97
- init();
98
- }, [client]);
102
+ } catch (err) {
103
+ console.error("BugBear: Init error", err);
104
+ } finally {
105
+ setIsLoading(false);
106
+ }
107
+ }, [client, refreshAssignments, refreshThreads]);
108
+ const refreshTesterStatus = useCallback(async () => {
109
+ await initializeBugBear();
110
+ }, [initializeBugBear]);
111
+ useEffect(() => {
112
+ if (enabled && !hasInitialized.current) {
113
+ hasInitialized.current = true;
114
+ initializeBugBear();
115
+ }
116
+ }, [enabled, initializeBugBear]);
117
+ useEffect(() => {
118
+ if (initCount > 0) {
119
+ initializeBugBear();
120
+ }
121
+ }, [initCount, initializeBugBear]);
99
122
  const currentAssignment = assignments.find(
100
123
  (a) => a.status === "in_progress"
101
124
  ) || assignments[0] || null;
@@ -120,7 +143,9 @@ function BugBearProvider({ config, children, appVersion }) {
120
143
  refreshThreads,
121
144
  getThreadMessages,
122
145
  sendMessage,
123
- markAsRead
146
+ markAsRead,
147
+ createThread,
148
+ refreshTesterStatus
124
149
  }
125
150
  },
126
151
  children
@@ -128,7 +153,7 @@ function BugBearProvider({ config, children, appVersion }) {
128
153
  }
129
154
 
130
155
  // src/BugBearButton.tsx
131
- import React2, { useState as useState2, useEffect as useEffect2, useRef } from "react";
156
+ import React2, { useState as useState2, useEffect as useEffect2, useRef as useRef2 } from "react";
132
157
  import {
133
158
  View,
134
159
  Text,
@@ -175,7 +200,8 @@ function BugBearButton({
175
200
  refreshThreads,
176
201
  getThreadMessages,
177
202
  sendMessage,
178
- markAsRead
203
+ markAsRead,
204
+ createThread
179
205
  } = useBugBear();
180
206
  const [modalVisible, setModalVisible] = useState2(false);
181
207
  const [activeTab, setActiveTab] = useState2("tests");
@@ -186,6 +212,9 @@ function BugBearButton({
186
212
  const [replyText, setReplyText] = useState2("");
187
213
  const [sendingReply, setSendingReply] = useState2(false);
188
214
  const [loadingMessages, setLoadingMessages] = useState2(false);
215
+ const [composeSubject, setComposeSubject] = useState2("");
216
+ const [composeMessage, setComposeMessage] = useState2("");
217
+ const [sendingNewMessage, setSendingNewMessage] = useState2(false);
189
218
  const getInitialPosition = () => {
190
219
  const buttonSize = 56;
191
220
  const margin = 16;
@@ -197,9 +226,9 @@ function BugBearButton({
197
226
  return { x, y };
198
227
  };
199
228
  const initialPos = getInitialPosition();
200
- const pan = useRef(new Animated.ValueXY(initialPos)).current;
201
- const isDragging = useRef(false);
202
- const panResponder = useRef(
229
+ const pan = useRef2(new Animated.ValueXY(initialPos)).current;
230
+ const isDragging = useRef2(false);
231
+ const panResponder = useRef2(
203
232
  PanResponder.create({
204
233
  onStartShouldSetPanResponder: () => draggable,
205
234
  onMoveShouldSetPanResponder: (_, gestureState) => {
@@ -348,6 +377,27 @@ function BugBearButton({
348
377
  setSelectedThread(null);
349
378
  setThreadMessages([]);
350
379
  setReplyText("");
380
+ setComposeSubject("");
381
+ setComposeMessage("");
382
+ };
383
+ const handleStartNewMessage = () => {
384
+ setMessageView("compose");
385
+ setComposeSubject("");
386
+ setComposeMessage("");
387
+ };
388
+ const handleSendNewMessage = async () => {
389
+ if (!composeSubject.trim() || !composeMessage.trim()) return;
390
+ setSendingNewMessage(true);
391
+ const result = await createThread({
392
+ subject: composeSubject.trim(),
393
+ message: composeMessage.trim()
394
+ });
395
+ if (result.success) {
396
+ setComposeSubject("");
397
+ setComposeMessage("");
398
+ setMessageView("list");
399
+ }
400
+ setSendingNewMessage(false);
351
401
  };
352
402
  const formatRelativeTime = (dateString) => {
353
403
  const date = new Date(dateString);
@@ -599,9 +649,46 @@ function BugBearButton({
599
649
  },
600
650
  /* @__PURE__ */ React2.createElement(Text, { style: styles.passButtonText }, submitting ? "..." : "\u2713 Pass")
601
651
  )))
602
- ) : null), activeTab === "messages" && /* @__PURE__ */ React2.createElement(View, null, messageView === "list" ? (
652
+ ) : null), activeTab === "messages" && /* @__PURE__ */ React2.createElement(View, null, messageView === "compose" ? (
653
+ /* Compose New Message View */
654
+ /* @__PURE__ */ React2.createElement(View, null, /* @__PURE__ */ React2.createElement(TouchableOpacity, { onPress: handleBackToThreadList, style: styles.backButton }, /* @__PURE__ */ React2.createElement(Text, { style: styles.backButtonText }, "\u2190 Back to Messages")), /* @__PURE__ */ React2.createElement(View, { style: styles.composeHeader }, /* @__PURE__ */ React2.createElement(Text, { style: styles.composeTitle }, "New Message"), /* @__PURE__ */ React2.createElement(Text, { style: styles.composeSubtitle }, "Send a message to the QA team")), /* @__PURE__ */ React2.createElement(View, { style: styles.composeForm }, /* @__PURE__ */ React2.createElement(Text, { style: styles.label }, "Subject"), /* @__PURE__ */ React2.createElement(
655
+ TextInput,
656
+ {
657
+ style: styles.composeSubjectInput,
658
+ value: composeSubject,
659
+ onChangeText: setComposeSubject,
660
+ placeholder: "What's this about?",
661
+ placeholderTextColor: "#9CA3AF",
662
+ maxLength: 100
663
+ }
664
+ ), /* @__PURE__ */ React2.createElement(Text, { style: [styles.label, { marginTop: 16 }] }, "Message"), /* @__PURE__ */ React2.createElement(
665
+ TextInput,
666
+ {
667
+ style: styles.composeMessageInput,
668
+ value: composeMessage,
669
+ onChangeText: setComposeMessage,
670
+ placeholder: "Write your message...",
671
+ placeholderTextColor: "#9CA3AF",
672
+ multiline: true,
673
+ numberOfLines: 6,
674
+ textAlignVertical: "top",
675
+ maxLength: 2e3
676
+ }
677
+ ), /* @__PURE__ */ React2.createElement(
678
+ TouchableOpacity,
679
+ {
680
+ style: [
681
+ styles.composeSendButton,
682
+ (!composeSubject.trim() || !composeMessage.trim() || sendingNewMessage) && styles.composeSendButtonDisabled
683
+ ],
684
+ onPress: handleSendNewMessage,
685
+ disabled: !composeSubject.trim() || !composeMessage.trim() || sendingNewMessage
686
+ },
687
+ /* @__PURE__ */ React2.createElement(Text, { style: styles.composeSendButtonText }, sendingNewMessage ? "Sending..." : "Send Message")
688
+ )))
689
+ ) : messageView === "list" ? (
603
690
  /* Thread List View */
604
- /* @__PURE__ */ React2.createElement(View, null, threads.length === 0 ? /* @__PURE__ */ React2.createElement(View, { style: styles.emptyState }, /* @__PURE__ */ React2.createElement(Text, { style: styles.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ React2.createElement(Text, { style: styles.emptyTitle }, "No messages yet"), /* @__PURE__ */ React2.createElement(Text, { style: styles.emptySubtitle }, "Messages from admins will appear here")) : /* @__PURE__ */ React2.createElement(View, null, threads.map((thread) => /* @__PURE__ */ React2.createElement(
691
+ /* @__PURE__ */ React2.createElement(View, null, /* @__PURE__ */ React2.createElement(TouchableOpacity, { onPress: handleStartNewMessage, style: styles.newMessageButton }, /* @__PURE__ */ React2.createElement(Text, { style: styles.newMessageButtonIcon }, "\u2709\uFE0F"), /* @__PURE__ */ React2.createElement(Text, { style: styles.newMessageButtonText }, "New Message")), threads.length === 0 ? /* @__PURE__ */ React2.createElement(View, { style: styles.emptyState }, /* @__PURE__ */ React2.createElement(Text, { style: styles.emptyEmoji }, "\u{1F4AC}"), /* @__PURE__ */ React2.createElement(Text, { style: styles.emptyTitle }, "No messages yet"), /* @__PURE__ */ React2.createElement(Text, { style: styles.emptySubtitle }, "Start a conversation or wait for messages from admins")) : /* @__PURE__ */ React2.createElement(View, null, threads.map((thread) => /* @__PURE__ */ React2.createElement(
605
692
  TouchableOpacity,
606
693
  {
607
694
  key: thread.id,
@@ -1631,6 +1718,81 @@ var styles = StyleSheet.create({
1631
1718
  fontSize: 14,
1632
1719
  fontWeight: "600",
1633
1720
  color: "#fff"
1721
+ },
1722
+ // New message button styles
1723
+ newMessageButton: {
1724
+ flexDirection: "row",
1725
+ alignItems: "center",
1726
+ justifyContent: "center",
1727
+ backgroundColor: "#7C3AED",
1728
+ paddingVertical: 12,
1729
+ paddingHorizontal: 20,
1730
+ borderRadius: 12,
1731
+ marginBottom: 16
1732
+ },
1733
+ newMessageButtonIcon: {
1734
+ fontSize: 16,
1735
+ marginRight: 8
1736
+ },
1737
+ newMessageButtonText: {
1738
+ fontSize: 15,
1739
+ fontWeight: "600",
1740
+ color: "#fff"
1741
+ },
1742
+ // Compose view styles
1743
+ composeHeader: {
1744
+ marginBottom: 20
1745
+ },
1746
+ composeTitle: {
1747
+ fontSize: 20,
1748
+ fontWeight: "600",
1749
+ color: "#111827",
1750
+ marginBottom: 4
1751
+ },
1752
+ composeSubtitle: {
1753
+ fontSize: 14,
1754
+ color: "#6B7280"
1755
+ },
1756
+ composeForm: {
1757
+ backgroundColor: "#F9FAFB",
1758
+ borderRadius: 12,
1759
+ padding: 16
1760
+ },
1761
+ composeSubjectInput: {
1762
+ backgroundColor: "#fff",
1763
+ borderWidth: 1,
1764
+ borderColor: "#E5E7EB",
1765
+ borderRadius: 8,
1766
+ paddingHorizontal: 12,
1767
+ paddingVertical: 10,
1768
+ fontSize: 15,
1769
+ color: "#111827"
1770
+ },
1771
+ composeMessageInput: {
1772
+ backgroundColor: "#fff",
1773
+ borderWidth: 1,
1774
+ borderColor: "#E5E7EB",
1775
+ borderRadius: 8,
1776
+ paddingHorizontal: 12,
1777
+ paddingVertical: 10,
1778
+ fontSize: 15,
1779
+ color: "#111827",
1780
+ minHeight: 120
1781
+ },
1782
+ composeSendButton: {
1783
+ backgroundColor: "#7C3AED",
1784
+ paddingVertical: 14,
1785
+ borderRadius: 12,
1786
+ alignItems: "center",
1787
+ marginTop: 20
1788
+ },
1789
+ composeSendButtonDisabled: {
1790
+ backgroundColor: "#C4B5FD"
1791
+ },
1792
+ composeSendButtonText: {
1793
+ fontSize: 16,
1794
+ fontWeight: "600",
1795
+ color: "#fff"
1634
1796
  }
1635
1797
  });
1636
1798
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/react-native",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "BugBear React Native components for mobile apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",