@bbearai/react-native 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -27,6 +27,15 @@ 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
+ }>;
30
39
  /** Re-check tester status (call after auth state changes) */
31
40
  refreshTesterStatus: () => Promise<void>;
32
41
  }
package/dist/index.d.ts CHANGED
@@ -27,6 +27,15 @@ 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
+ }>;
30
39
  /** Re-check tester status (call after auth state changes) */
31
40
  refreshTesterStatus: () => Promise<void>;
32
41
  }
package/dist/index.js CHANGED
@@ -61,6 +61,7 @@ var BugBearContext = (0, import_react.createContext)({
61
61
  sendMessage: async () => false,
62
62
  markAsRead: async () => {
63
63
  },
64
+ createThread: async () => ({ success: false }),
64
65
  refreshTesterStatus: async () => {
65
66
  }
66
67
  });
@@ -68,7 +69,7 @@ function useBugBear() {
68
69
  return (0, import_react.useContext)(BugBearContext);
69
70
  }
70
71
  function BugBearProvider({ config, children, appVersion, enabled = true }) {
71
- const [client] = (0, import_react.useState)(() => (0, import_core.createBugBear)(config));
72
+ const [client, setClient] = (0, import_react.useState)(null);
72
73
  const [isTester, setIsTester] = (0, import_react.useState)(false);
73
74
  const [isQAEnabled, setIsQAEnabled] = (0, import_react.useState)(false);
74
75
  const [testerInfo, setTesterInfo] = (0, import_react.useState)(null);
@@ -76,7 +77,6 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
76
77
  const [isLoading, setIsLoading] = (0, import_react.useState)(true);
77
78
  const [threads, setThreads] = (0, import_react.useState)([]);
78
79
  const [unreadCount, setUnreadCount] = (0, import_react.useState)(0);
79
- const [initCount, setInitCount] = (0, import_react.useState)(0);
80
80
  const hasInitialized = (0, import_react.useRef)(false);
81
81
  const getDeviceInfo = (0, import_react.useCallback)(() => {
82
82
  const { width, height } = import_react_native.Dimensions.get("window");
@@ -88,19 +88,23 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
88
88
  };
89
89
  }, [appVersion]);
90
90
  const refreshAssignments = (0, import_react.useCallback)(async () => {
91
+ if (!client) return;
91
92
  const newAssignments = await client.getAssignedTests();
92
93
  setAssignments(newAssignments);
93
94
  }, [client]);
94
95
  const refreshThreads = (0, import_react.useCallback)(async () => {
96
+ if (!client) return;
95
97
  const newThreads = await client.getThreadsForTester();
96
98
  setThreads(newThreads);
97
99
  const totalUnread = newThreads.reduce((sum, t) => sum + t.unreadCount, 0);
98
100
  setUnreadCount(totalUnread);
99
101
  }, [client]);
100
102
  const getThreadMessages = (0, import_react.useCallback)(async (threadId) => {
103
+ if (!client) return [];
101
104
  return client.getThreadMessages(threadId);
102
105
  }, [client]);
103
106
  const sendMessage = (0, import_react.useCallback)(async (threadId, content) => {
107
+ if (!client) return false;
104
108
  const success = await client.sendMessage(threadId, content);
105
109
  if (success) {
106
110
  await refreshThreads();
@@ -108,45 +112,58 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
108
112
  return success;
109
113
  }, [client, refreshThreads]);
110
114
  const markAsRead = (0, import_react.useCallback)(async (threadId) => {
115
+ if (!client) return;
111
116
  await client.markThreadAsRead(threadId);
112
117
  await refreshThreads();
113
118
  }, [client, refreshThreads]);
114
- const initializeBugBear = (0, import_react.useCallback)(async () => {
119
+ const createThread = (0, import_react.useCallback)(async (options) => {
120
+ if (!client) return { success: false, error: "Client not initialized" };
121
+ const result = await client.createThread(options);
122
+ if (result.success) {
123
+ await refreshThreads();
124
+ }
125
+ return result;
126
+ }, [client, refreshThreads]);
127
+ const initializeBugBear = (0, import_react.useCallback)(async (bugBearClient) => {
115
128
  setIsLoading(true);
116
129
  try {
117
130
  const [qaEnabled, info] = await Promise.all([
118
- client.isQAEnabled(),
119
- client.getTesterInfo()
131
+ bugBearClient.isQAEnabled(),
132
+ bugBearClient.getTesterInfo()
120
133
  ]);
134
+ console.log("BugBear: Init complete", { qaEnabled, testerInfo: info });
121
135
  setIsQAEnabled(qaEnabled);
122
136
  setTesterInfo(info);
123
137
  setIsTester(!!info);
124
138
  if (info && qaEnabled) {
125
- await Promise.all([
126
- refreshAssignments(),
127
- refreshThreads()
139
+ const [newAssignments, newThreads] = await Promise.all([
140
+ bugBearClient.getAssignedTests(),
141
+ bugBearClient.getThreadsForTester()
128
142
  ]);
143
+ setAssignments(newAssignments);
144
+ setThreads(newThreads);
145
+ const totalUnread = newThreads.reduce((sum, t) => sum + t.unreadCount, 0);
146
+ setUnreadCount(totalUnread);
129
147
  }
130
148
  } catch (err) {
131
149
  console.error("BugBear: Init error", err);
132
150
  } finally {
133
151
  setIsLoading(false);
134
152
  }
135
- }, [client, refreshAssignments, refreshThreads]);
153
+ }, []);
136
154
  const refreshTesterStatus = (0, import_react.useCallback)(async () => {
137
- await initializeBugBear();
138
- }, [initializeBugBear]);
155
+ const freshClient = (0, import_core.createBugBear)(config);
156
+ setClient(freshClient);
157
+ await initializeBugBear(freshClient);
158
+ }, [config, initializeBugBear]);
139
159
  (0, import_react.useEffect)(() => {
140
160
  if (enabled && !hasInitialized.current) {
141
161
  hasInitialized.current = true;
142
- initializeBugBear();
162
+ const newClient = (0, import_core.createBugBear)(config);
163
+ setClient(newClient);
164
+ initializeBugBear(newClient);
143
165
  }
144
- }, [enabled, initializeBugBear]);
145
- (0, import_react.useEffect)(() => {
146
- if (initCount > 0) {
147
- initializeBugBear();
148
- }
149
- }, [initCount, initializeBugBear]);
166
+ }, [enabled, config, initializeBugBear]);
150
167
  const currentAssignment = assignments.find(
151
168
  (a) => a.status === "in_progress"
152
169
  ) || assignments[0] || null;
@@ -172,6 +189,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
172
189
  getThreadMessages,
173
190
  sendMessage,
174
191
  markAsRead,
192
+ createThread,
175
193
  refreshTesterStatus
176
194
  }
177
195
  },
@@ -214,7 +232,8 @@ function BugBearButton({
214
232
  refreshThreads,
215
233
  getThreadMessages,
216
234
  sendMessage,
217
- markAsRead
235
+ markAsRead,
236
+ createThread
218
237
  } = useBugBear();
219
238
  const [modalVisible, setModalVisible] = (0, import_react2.useState)(false);
220
239
  const [activeTab, setActiveTab] = (0, import_react2.useState)("tests");
@@ -225,6 +244,9 @@ function BugBearButton({
225
244
  const [replyText, setReplyText] = (0, import_react2.useState)("");
226
245
  const [sendingReply, setSendingReply] = (0, import_react2.useState)(false);
227
246
  const [loadingMessages, setLoadingMessages] = (0, import_react2.useState)(false);
247
+ const [composeSubject, setComposeSubject] = (0, import_react2.useState)("");
248
+ const [composeMessage, setComposeMessage] = (0, import_react2.useState)("");
249
+ const [sendingNewMessage, setSendingNewMessage] = (0, import_react2.useState)(false);
228
250
  const getInitialPosition = () => {
229
251
  const buttonSize = 56;
230
252
  const margin = 16;
@@ -387,6 +409,27 @@ function BugBearButton({
387
409
  setSelectedThread(null);
388
410
  setThreadMessages([]);
389
411
  setReplyText("");
412
+ setComposeSubject("");
413
+ setComposeMessage("");
414
+ };
415
+ const handleStartNewMessage = () => {
416
+ setMessageView("compose");
417
+ setComposeSubject("");
418
+ setComposeMessage("");
419
+ };
420
+ const handleSendNewMessage = async () => {
421
+ if (!composeSubject.trim() || !composeMessage.trim()) return;
422
+ setSendingNewMessage(true);
423
+ const result = await createThread({
424
+ subject: composeSubject.trim(),
425
+ message: composeMessage.trim()
426
+ });
427
+ if (result.success) {
428
+ setComposeSubject("");
429
+ setComposeMessage("");
430
+ setMessageView("list");
431
+ }
432
+ setSendingNewMessage(false);
390
433
  };
391
434
  const formatRelativeTime = (dateString) => {
392
435
  const date = new Date(dateString);
@@ -638,9 +681,46 @@ function BugBearButton({
638
681
  },
639
682
  /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.passButtonText }, submitting ? "..." : "\u2713 Pass")
640
683
  )))
641
- ) : null), activeTab === "messages" && /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, null, messageView === "list" ? (
684
+ ) : null), activeTab === "messages" && /* @__PURE__ */ import_react2.default.createElement(import_react_native2.View, null, messageView === "compose" ? (
685
+ /* Compose New Message View */
686
+ /* @__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(
687
+ import_react_native2.TextInput,
688
+ {
689
+ style: styles.composeSubjectInput,
690
+ value: composeSubject,
691
+ onChangeText: setComposeSubject,
692
+ placeholder: "What's this about?",
693
+ placeholderTextColor: "#9CA3AF",
694
+ maxLength: 100
695
+ }
696
+ ), /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: [styles.label, { marginTop: 16 }] }, "Message"), /* @__PURE__ */ import_react2.default.createElement(
697
+ import_react_native2.TextInput,
698
+ {
699
+ style: styles.composeMessageInput,
700
+ value: composeMessage,
701
+ onChangeText: setComposeMessage,
702
+ placeholder: "Write your message...",
703
+ placeholderTextColor: "#9CA3AF",
704
+ multiline: true,
705
+ numberOfLines: 6,
706
+ textAlignVertical: "top",
707
+ maxLength: 2e3
708
+ }
709
+ ), /* @__PURE__ */ import_react2.default.createElement(
710
+ import_react_native2.TouchableOpacity,
711
+ {
712
+ style: [
713
+ styles.composeSendButton,
714
+ (!composeSubject.trim() || !composeMessage.trim() || sendingNewMessage) && styles.composeSendButtonDisabled
715
+ ],
716
+ onPress: handleSendNewMessage,
717
+ disabled: !composeSubject.trim() || !composeMessage.trim() || sendingNewMessage
718
+ },
719
+ /* @__PURE__ */ import_react2.default.createElement(import_react_native2.Text, { style: styles.composeSendButtonText }, sendingNewMessage ? "Sending..." : "Send Message")
720
+ )))
721
+ ) : messageView === "list" ? (
642
722
  /* Thread List View */
643
- /* @__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(
723
+ /* @__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(
644
724
  import_react_native2.TouchableOpacity,
645
725
  {
646
726
  key: thread.id,
@@ -1670,6 +1750,81 @@ var styles = import_react_native2.StyleSheet.create({
1670
1750
  fontSize: 14,
1671
1751
  fontWeight: "600",
1672
1752
  color: "#fff"
1753
+ },
1754
+ // New message button styles
1755
+ newMessageButton: {
1756
+ flexDirection: "row",
1757
+ alignItems: "center",
1758
+ justifyContent: "center",
1759
+ backgroundColor: "#7C3AED",
1760
+ paddingVertical: 12,
1761
+ paddingHorizontal: 20,
1762
+ borderRadius: 12,
1763
+ marginBottom: 16
1764
+ },
1765
+ newMessageButtonIcon: {
1766
+ fontSize: 16,
1767
+ marginRight: 8
1768
+ },
1769
+ newMessageButtonText: {
1770
+ fontSize: 15,
1771
+ fontWeight: "600",
1772
+ color: "#fff"
1773
+ },
1774
+ // Compose view styles
1775
+ composeHeader: {
1776
+ marginBottom: 20
1777
+ },
1778
+ composeTitle: {
1779
+ fontSize: 20,
1780
+ fontWeight: "600",
1781
+ color: "#111827",
1782
+ marginBottom: 4
1783
+ },
1784
+ composeSubtitle: {
1785
+ fontSize: 14,
1786
+ color: "#6B7280"
1787
+ },
1788
+ composeForm: {
1789
+ backgroundColor: "#F9FAFB",
1790
+ borderRadius: 12,
1791
+ padding: 16
1792
+ },
1793
+ composeSubjectInput: {
1794
+ backgroundColor: "#fff",
1795
+ borderWidth: 1,
1796
+ borderColor: "#E5E7EB",
1797
+ borderRadius: 8,
1798
+ paddingHorizontal: 12,
1799
+ paddingVertical: 10,
1800
+ fontSize: 15,
1801
+ color: "#111827"
1802
+ },
1803
+ composeMessageInput: {
1804
+ backgroundColor: "#fff",
1805
+ borderWidth: 1,
1806
+ borderColor: "#E5E7EB",
1807
+ borderRadius: 8,
1808
+ paddingHorizontal: 12,
1809
+ paddingVertical: 10,
1810
+ fontSize: 15,
1811
+ color: "#111827",
1812
+ minHeight: 120
1813
+ },
1814
+ composeSendButton: {
1815
+ backgroundColor: "#7C3AED",
1816
+ paddingVertical: 14,
1817
+ borderRadius: 12,
1818
+ alignItems: "center",
1819
+ marginTop: 20
1820
+ },
1821
+ composeSendButtonDisabled: {
1822
+ backgroundColor: "#C4B5FD"
1823
+ },
1824
+ composeSendButtonText: {
1825
+ fontSize: 16,
1826
+ fontWeight: "600",
1827
+ color: "#fff"
1673
1828
  }
1674
1829
  });
1675
1830
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.mjs CHANGED
@@ -25,6 +25,7 @@ var BugBearContext = createContext({
25
25
  sendMessage: async () => false,
26
26
  markAsRead: async () => {
27
27
  },
28
+ createThread: async () => ({ success: false }),
28
29
  refreshTesterStatus: async () => {
29
30
  }
30
31
  });
@@ -32,7 +33,7 @@ function useBugBear() {
32
33
  return useContext(BugBearContext);
33
34
  }
34
35
  function BugBearProvider({ config, children, appVersion, enabled = true }) {
35
- const [client] = useState(() => createBugBear(config));
36
+ const [client, setClient] = useState(null);
36
37
  const [isTester, setIsTester] = useState(false);
37
38
  const [isQAEnabled, setIsQAEnabled] = useState(false);
38
39
  const [testerInfo, setTesterInfo] = useState(null);
@@ -40,7 +41,6 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
40
41
  const [isLoading, setIsLoading] = useState(true);
41
42
  const [threads, setThreads] = useState([]);
42
43
  const [unreadCount, setUnreadCount] = useState(0);
43
- const [initCount, setInitCount] = useState(0);
44
44
  const hasInitialized = useRef(false);
45
45
  const getDeviceInfo = useCallback(() => {
46
46
  const { width, height } = Dimensions.get("window");
@@ -52,19 +52,23 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
52
52
  };
53
53
  }, [appVersion]);
54
54
  const refreshAssignments = useCallback(async () => {
55
+ if (!client) return;
55
56
  const newAssignments = await client.getAssignedTests();
56
57
  setAssignments(newAssignments);
57
58
  }, [client]);
58
59
  const refreshThreads = useCallback(async () => {
60
+ if (!client) return;
59
61
  const newThreads = await client.getThreadsForTester();
60
62
  setThreads(newThreads);
61
63
  const totalUnread = newThreads.reduce((sum, t) => sum + t.unreadCount, 0);
62
64
  setUnreadCount(totalUnread);
63
65
  }, [client]);
64
66
  const getThreadMessages = useCallback(async (threadId) => {
67
+ if (!client) return [];
65
68
  return client.getThreadMessages(threadId);
66
69
  }, [client]);
67
70
  const sendMessage = useCallback(async (threadId, content) => {
71
+ if (!client) return false;
68
72
  const success = await client.sendMessage(threadId, content);
69
73
  if (success) {
70
74
  await refreshThreads();
@@ -72,45 +76,58 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
72
76
  return success;
73
77
  }, [client, refreshThreads]);
74
78
  const markAsRead = useCallback(async (threadId) => {
79
+ if (!client) return;
75
80
  await client.markThreadAsRead(threadId);
76
81
  await refreshThreads();
77
82
  }, [client, refreshThreads]);
78
- const initializeBugBear = useCallback(async () => {
83
+ const createThread = useCallback(async (options) => {
84
+ if (!client) return { success: false, error: "Client not initialized" };
85
+ const result = await client.createThread(options);
86
+ if (result.success) {
87
+ await refreshThreads();
88
+ }
89
+ return result;
90
+ }, [client, refreshThreads]);
91
+ const initializeBugBear = useCallback(async (bugBearClient) => {
79
92
  setIsLoading(true);
80
93
  try {
81
94
  const [qaEnabled, info] = await Promise.all([
82
- client.isQAEnabled(),
83
- client.getTesterInfo()
95
+ bugBearClient.isQAEnabled(),
96
+ bugBearClient.getTesterInfo()
84
97
  ]);
98
+ console.log("BugBear: Init complete", { qaEnabled, testerInfo: info });
85
99
  setIsQAEnabled(qaEnabled);
86
100
  setTesterInfo(info);
87
101
  setIsTester(!!info);
88
102
  if (info && qaEnabled) {
89
- await Promise.all([
90
- refreshAssignments(),
91
- refreshThreads()
103
+ const [newAssignments, newThreads] = await Promise.all([
104
+ bugBearClient.getAssignedTests(),
105
+ bugBearClient.getThreadsForTester()
92
106
  ]);
107
+ setAssignments(newAssignments);
108
+ setThreads(newThreads);
109
+ const totalUnread = newThreads.reduce((sum, t) => sum + t.unreadCount, 0);
110
+ setUnreadCount(totalUnread);
93
111
  }
94
112
  } catch (err) {
95
113
  console.error("BugBear: Init error", err);
96
114
  } finally {
97
115
  setIsLoading(false);
98
116
  }
99
- }, [client, refreshAssignments, refreshThreads]);
117
+ }, []);
100
118
  const refreshTesterStatus = useCallback(async () => {
101
- await initializeBugBear();
102
- }, [initializeBugBear]);
119
+ const freshClient = createBugBear(config);
120
+ setClient(freshClient);
121
+ await initializeBugBear(freshClient);
122
+ }, [config, initializeBugBear]);
103
123
  useEffect(() => {
104
124
  if (enabled && !hasInitialized.current) {
105
125
  hasInitialized.current = true;
106
- initializeBugBear();
126
+ const newClient = createBugBear(config);
127
+ setClient(newClient);
128
+ initializeBugBear(newClient);
107
129
  }
108
- }, [enabled, initializeBugBear]);
109
- useEffect(() => {
110
- if (initCount > 0) {
111
- initializeBugBear();
112
- }
113
- }, [initCount, initializeBugBear]);
130
+ }, [enabled, config, initializeBugBear]);
114
131
  const currentAssignment = assignments.find(
115
132
  (a) => a.status === "in_progress"
116
133
  ) || assignments[0] || null;
@@ -136,6 +153,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
136
153
  getThreadMessages,
137
154
  sendMessage,
138
155
  markAsRead,
156
+ createThread,
139
157
  refreshTesterStatus
140
158
  }
141
159
  },
@@ -191,7 +209,8 @@ function BugBearButton({
191
209
  refreshThreads,
192
210
  getThreadMessages,
193
211
  sendMessage,
194
- markAsRead
212
+ markAsRead,
213
+ createThread
195
214
  } = useBugBear();
196
215
  const [modalVisible, setModalVisible] = useState2(false);
197
216
  const [activeTab, setActiveTab] = useState2("tests");
@@ -202,6 +221,9 @@ function BugBearButton({
202
221
  const [replyText, setReplyText] = useState2("");
203
222
  const [sendingReply, setSendingReply] = useState2(false);
204
223
  const [loadingMessages, setLoadingMessages] = useState2(false);
224
+ const [composeSubject, setComposeSubject] = useState2("");
225
+ const [composeMessage, setComposeMessage] = useState2("");
226
+ const [sendingNewMessage, setSendingNewMessage] = useState2(false);
205
227
  const getInitialPosition = () => {
206
228
  const buttonSize = 56;
207
229
  const margin = 16;
@@ -364,6 +386,27 @@ function BugBearButton({
364
386
  setSelectedThread(null);
365
387
  setThreadMessages([]);
366
388
  setReplyText("");
389
+ setComposeSubject("");
390
+ setComposeMessage("");
391
+ };
392
+ const handleStartNewMessage = () => {
393
+ setMessageView("compose");
394
+ setComposeSubject("");
395
+ setComposeMessage("");
396
+ };
397
+ const handleSendNewMessage = async () => {
398
+ if (!composeSubject.trim() || !composeMessage.trim()) return;
399
+ setSendingNewMessage(true);
400
+ const result = await createThread({
401
+ subject: composeSubject.trim(),
402
+ message: composeMessage.trim()
403
+ });
404
+ if (result.success) {
405
+ setComposeSubject("");
406
+ setComposeMessage("");
407
+ setMessageView("list");
408
+ }
409
+ setSendingNewMessage(false);
367
410
  };
368
411
  const formatRelativeTime = (dateString) => {
369
412
  const date = new Date(dateString);
@@ -615,9 +658,46 @@ function BugBearButton({
615
658
  },
616
659
  /* @__PURE__ */ React2.createElement(Text, { style: styles.passButtonText }, submitting ? "..." : "\u2713 Pass")
617
660
  )))
618
- ) : null), activeTab === "messages" && /* @__PURE__ */ React2.createElement(View, null, messageView === "list" ? (
661
+ ) : null), activeTab === "messages" && /* @__PURE__ */ React2.createElement(View, null, messageView === "compose" ? (
662
+ /* Compose New Message View */
663
+ /* @__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(
664
+ TextInput,
665
+ {
666
+ style: styles.composeSubjectInput,
667
+ value: composeSubject,
668
+ onChangeText: setComposeSubject,
669
+ placeholder: "What's this about?",
670
+ placeholderTextColor: "#9CA3AF",
671
+ maxLength: 100
672
+ }
673
+ ), /* @__PURE__ */ React2.createElement(Text, { style: [styles.label, { marginTop: 16 }] }, "Message"), /* @__PURE__ */ React2.createElement(
674
+ TextInput,
675
+ {
676
+ style: styles.composeMessageInput,
677
+ value: composeMessage,
678
+ onChangeText: setComposeMessage,
679
+ placeholder: "Write your message...",
680
+ placeholderTextColor: "#9CA3AF",
681
+ multiline: true,
682
+ numberOfLines: 6,
683
+ textAlignVertical: "top",
684
+ maxLength: 2e3
685
+ }
686
+ ), /* @__PURE__ */ React2.createElement(
687
+ TouchableOpacity,
688
+ {
689
+ style: [
690
+ styles.composeSendButton,
691
+ (!composeSubject.trim() || !composeMessage.trim() || sendingNewMessage) && styles.composeSendButtonDisabled
692
+ ],
693
+ onPress: handleSendNewMessage,
694
+ disabled: !composeSubject.trim() || !composeMessage.trim() || sendingNewMessage
695
+ },
696
+ /* @__PURE__ */ React2.createElement(Text, { style: styles.composeSendButtonText }, sendingNewMessage ? "Sending..." : "Send Message")
697
+ )))
698
+ ) : messageView === "list" ? (
619
699
  /* Thread List View */
620
- /* @__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(
700
+ /* @__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(
621
701
  TouchableOpacity,
622
702
  {
623
703
  key: thread.id,
@@ -1647,6 +1727,81 @@ var styles = StyleSheet.create({
1647
1727
  fontSize: 14,
1648
1728
  fontWeight: "600",
1649
1729
  color: "#fff"
1730
+ },
1731
+ // New message button styles
1732
+ newMessageButton: {
1733
+ flexDirection: "row",
1734
+ alignItems: "center",
1735
+ justifyContent: "center",
1736
+ backgroundColor: "#7C3AED",
1737
+ paddingVertical: 12,
1738
+ paddingHorizontal: 20,
1739
+ borderRadius: 12,
1740
+ marginBottom: 16
1741
+ },
1742
+ newMessageButtonIcon: {
1743
+ fontSize: 16,
1744
+ marginRight: 8
1745
+ },
1746
+ newMessageButtonText: {
1747
+ fontSize: 15,
1748
+ fontWeight: "600",
1749
+ color: "#fff"
1750
+ },
1751
+ // Compose view styles
1752
+ composeHeader: {
1753
+ marginBottom: 20
1754
+ },
1755
+ composeTitle: {
1756
+ fontSize: 20,
1757
+ fontWeight: "600",
1758
+ color: "#111827",
1759
+ marginBottom: 4
1760
+ },
1761
+ composeSubtitle: {
1762
+ fontSize: 14,
1763
+ color: "#6B7280"
1764
+ },
1765
+ composeForm: {
1766
+ backgroundColor: "#F9FAFB",
1767
+ borderRadius: 12,
1768
+ padding: 16
1769
+ },
1770
+ composeSubjectInput: {
1771
+ backgroundColor: "#fff",
1772
+ borderWidth: 1,
1773
+ borderColor: "#E5E7EB",
1774
+ borderRadius: 8,
1775
+ paddingHorizontal: 12,
1776
+ paddingVertical: 10,
1777
+ fontSize: 15,
1778
+ color: "#111827"
1779
+ },
1780
+ composeMessageInput: {
1781
+ backgroundColor: "#fff",
1782
+ borderWidth: 1,
1783
+ borderColor: "#E5E7EB",
1784
+ borderRadius: 8,
1785
+ paddingHorizontal: 12,
1786
+ paddingVertical: 10,
1787
+ fontSize: 15,
1788
+ color: "#111827",
1789
+ minHeight: 120
1790
+ },
1791
+ composeSendButton: {
1792
+ backgroundColor: "#7C3AED",
1793
+ paddingVertical: 14,
1794
+ borderRadius: 12,
1795
+ alignItems: "center",
1796
+ marginTop: 20
1797
+ },
1798
+ composeSendButtonDisabled: {
1799
+ backgroundColor: "#C4B5FD"
1800
+ },
1801
+ composeSendButtonText: {
1802
+ fontSize: 16,
1803
+ fontWeight: "600",
1804
+ color: "#fff"
1650
1805
  }
1651
1806
  });
1652
1807
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/react-native",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "BugBear React Native components for mobile apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",