@bbearai/react 0.1.5 → 0.1.7

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
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, Component, ErrorInfo } from 'react';
3
3
  import * as _bbearai_core from '@bbearai/core';
4
- import { BugBearConfig, BugBearClient, TesterInfo, TestAssignment, AppContext, captureError } from '@bbearai/core';
4
+ import { BugBearConfig, BugBearClient, TesterInfo, TestAssignment, TesterProfileUpdate, AppContext, captureError } from '@bbearai/core';
5
5
  export { AppContext, BugBearConfig, BugBearReport, ConsoleLogEntry, DeviceInfo, EnhancedBugContext, NetworkRequest, QATrack, ReportType, Severity, TestAssignment, TestTemplate, TesterInfo, captureError, contextCapture } from '@bbearai/core';
6
6
 
7
7
  interface BugBearContextValue {
@@ -18,6 +18,13 @@ interface BugBearContextValue {
18
18
  onNavigate?: (route: string) => void;
19
19
  /** Re-check tester status (call after auth state changes) */
20
20
  refreshTesterStatus: () => Promise<void>;
21
+ /** Update tester profile */
22
+ updateTesterProfile: (updates: TesterProfileUpdate) => Promise<{
23
+ success: boolean;
24
+ error?: string;
25
+ }>;
26
+ /** Refresh tester info from server */
27
+ refreshTesterInfo: () => Promise<void>;
21
28
  }
22
29
  declare function useBugBear(): BugBearContextValue;
23
30
  interface BugBearProviderProps {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, Component, ErrorInfo } from 'react';
3
3
  import * as _bbearai_core from '@bbearai/core';
4
- import { BugBearConfig, BugBearClient, TesterInfo, TestAssignment, AppContext, captureError } from '@bbearai/core';
4
+ import { BugBearConfig, BugBearClient, TesterInfo, TestAssignment, TesterProfileUpdate, AppContext, captureError } from '@bbearai/core';
5
5
  export { AppContext, BugBearConfig, BugBearReport, ConsoleLogEntry, DeviceInfo, EnhancedBugContext, NetworkRequest, QATrack, ReportType, Severity, TestAssignment, TestTemplate, TesterInfo, captureError, contextCapture } from '@bbearai/core';
6
6
 
7
7
  interface BugBearContextValue {
@@ -18,6 +18,13 @@ interface BugBearContextValue {
18
18
  onNavigate?: (route: string) => void;
19
19
  /** Re-check tester status (call after auth state changes) */
20
20
  refreshTesterStatus: () => Promise<void>;
21
+ /** Update tester profile */
22
+ updateTesterProfile: (updates: TesterProfileUpdate) => Promise<{
23
+ success: boolean;
24
+ error?: string;
25
+ }>;
26
+ /** Refresh tester info from server */
27
+ refreshTesterInfo: () => Promise<void>;
21
28
  }
22
29
  declare function useBugBear(): BugBearContextValue;
23
30
  interface BugBearProviderProps {
package/dist/index.js CHANGED
@@ -47,6 +47,9 @@ var BugBearContext = (0, import_react.createContext)({
47
47
  isLoading: true,
48
48
  onNavigate: void 0,
49
49
  refreshTesterStatus: async () => {
50
+ },
51
+ updateTesterProfile: async () => ({ success: false }),
52
+ refreshTesterInfo: async () => {
50
53
  }
51
54
  });
52
55
  function useBugBear() {
@@ -91,6 +94,20 @@ function BugBearProvider({ config, children, enabled = true }) {
91
94
  setClient(freshClient);
92
95
  await initializeBugBear(freshClient);
93
96
  }, [config, initializeBugBear]);
97
+ const updateTesterProfile = (0, import_react.useCallback)(async (updates) => {
98
+ if (!client) return { success: false, error: "Client not initialized" };
99
+ const result = await client.updateTesterProfile(updates);
100
+ if (result.success) {
101
+ const info = await client.getTesterInfo();
102
+ setTesterInfo(info);
103
+ }
104
+ return result;
105
+ }, [client]);
106
+ const refreshTesterInfo = (0, import_react.useCallback)(async () => {
107
+ if (!client) return;
108
+ const info = await client.getTesterInfo();
109
+ setTesterInfo(info);
110
+ }, [client]);
94
111
  (0, import_react.useEffect)(() => {
95
112
  if (enabled && !hasInitialized.current) {
96
113
  hasInitialized.current = true;
@@ -117,7 +134,9 @@ function BugBearProvider({ config, children, enabled = true }) {
117
134
  refreshAssignments,
118
135
  isLoading,
119
136
  onNavigate: config.onNavigate,
120
- refreshTesterStatus
137
+ refreshTesterStatus,
138
+ updateTesterProfile,
139
+ refreshTesterInfo
121
140
  },
122
141
  children
123
142
  }
@@ -196,7 +215,7 @@ function BugBearPanel({
196
215
  defaultCollapsed = false,
197
216
  draggable = true
198
217
  }) {
199
- const { client, shouldShowWidget, testerInfo, assignments, currentAssignment, refreshAssignments, isLoading, onNavigate } = useBugBear();
218
+ const { client, shouldShowWidget, testerInfo, assignments, currentAssignment, refreshAssignments, isLoading, onNavigate, updateTesterProfile, refreshTesterInfo } = useBugBear();
200
219
  const [collapsed, setCollapsed] = (0, import_react2.useState)(defaultCollapsed);
201
220
  const [activeTab, setActiveTab] = (0, import_react2.useState)("tests");
202
221
  const [showSteps, setShowSteps] = (0, import_react2.useState)(false);
@@ -214,6 +233,14 @@ function BugBearPanel({
214
233
  const [submitted, setSubmitted] = (0, import_react2.useState)(false);
215
234
  const [justPassed, setJustPassed] = (0, import_react2.useState)(false);
216
235
  const [criteriaResults, setCriteriaResults] = (0, import_react2.useState)({});
236
+ const [profileEditing, setProfileEditing] = (0, import_react2.useState)(false);
237
+ const [profileName, setProfileName] = (0, import_react2.useState)("");
238
+ const [profileAdditionalEmails, setProfileAdditionalEmails] = (0, import_react2.useState)([]);
239
+ const [newEmailInput, setNewEmailInput] = (0, import_react2.useState)("");
240
+ const [profilePlatforms, setProfilePlatforms] = (0, import_react2.useState)([]);
241
+ const [savingProfile, setSavingProfile] = (0, import_react2.useState)(false);
242
+ const [profileSaved, setProfileSaved] = (0, import_react2.useState)(false);
243
+ const [showProfileOverlay, setShowProfileOverlay] = (0, import_react2.useState)(false);
217
244
  (0, import_react2.useEffect)(() => {
218
245
  if (typeof window === "undefined") return;
219
246
  try {
@@ -350,6 +377,67 @@ function BugBearPanel({
350
377
  };
351
378
  const pendingCount = assignments.filter((a) => a.status === "pending").length;
352
379
  const inProgressCount = assignments.filter((a) => a.status === "in_progress").length;
380
+ const handleOpenProfile = () => {
381
+ if (testerInfo) {
382
+ setProfileName(testerInfo.name);
383
+ setProfileAdditionalEmails(testerInfo.additionalEmails || []);
384
+ setProfilePlatforms(testerInfo.platforms || []);
385
+ }
386
+ setProfileEditing(false);
387
+ setShowProfileOverlay(true);
388
+ };
389
+ const handleStartEditProfile = () => {
390
+ if (testerInfo) {
391
+ setProfileName(testerInfo.name);
392
+ setProfileAdditionalEmails(testerInfo.additionalEmails || []);
393
+ setProfilePlatforms(testerInfo.platforms || []);
394
+ }
395
+ setProfileEditing(true);
396
+ };
397
+ const handleCancelEditProfile = () => {
398
+ setProfileEditing(false);
399
+ setNewEmailInput("");
400
+ };
401
+ const handleCloseProfile = () => {
402
+ setShowProfileOverlay(false);
403
+ setProfileEditing(false);
404
+ setNewEmailInput("");
405
+ };
406
+ const handleAddEmail = () => {
407
+ const email = newEmailInput.trim().toLowerCase();
408
+ if (email && email.includes("@") && !profileAdditionalEmails.includes(email)) {
409
+ setProfileAdditionalEmails([...profileAdditionalEmails, email]);
410
+ setNewEmailInput("");
411
+ }
412
+ };
413
+ const handleRemoveEmail = (email) => {
414
+ setProfileAdditionalEmails(profileAdditionalEmails.filter((e) => e !== email));
415
+ };
416
+ const handleTogglePlatform = (platform) => {
417
+ if (profilePlatforms.includes(platform)) {
418
+ setProfilePlatforms(profilePlatforms.filter((p) => p !== platform));
419
+ } else {
420
+ setProfilePlatforms([...profilePlatforms, platform]);
421
+ }
422
+ };
423
+ const handleSaveProfile = async () => {
424
+ setSavingProfile(true);
425
+ const updates = {
426
+ name: profileName.trim(),
427
+ additionalEmails: profileAdditionalEmails,
428
+ platforms: profilePlatforms
429
+ };
430
+ const result = await updateTesterProfile(updates);
431
+ if (result.success) {
432
+ setProfileEditing(false);
433
+ setProfileSaved(true);
434
+ setTimeout(() => {
435
+ setProfileSaved(false);
436
+ setShowProfileOverlay(false);
437
+ }, 1500);
438
+ }
439
+ setSavingProfile(false);
440
+ };
353
441
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
354
442
  "div",
355
443
  {
@@ -397,7 +485,17 @@ function BugBearPanel({
397
485
  "BugBear",
398
486
  draggable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-purple-300 text-xs", title: "Drag to move, double-click to reset", children: "\u22EE\u22EE" })
399
487
  ] }),
400
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-purple-200 text-xs", children: testerInfo?.name })
488
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
489
+ "button",
490
+ {
491
+ onClick: handleOpenProfile,
492
+ className: "text-purple-200 text-xs flex items-center gap-1 hover:text-white transition-colors",
493
+ children: [
494
+ testerInfo?.name,
495
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px]", children: "\u270E" })
496
+ ]
497
+ }
498
+ )
401
499
  ] })
402
500
  ] }),
403
501
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -752,6 +850,165 @@ function BugBearPanel({
752
850
  )
753
851
  ] }) })
754
852
  ] }),
853
+ showProfileOverlay && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "absolute inset-0 bg-white z-50 flex flex-col rounded-xl overflow-hidden", children: [
854
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "bg-purple-600 text-white px-4 py-3 flex items-center justify-between", children: [
855
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
856
+ "button",
857
+ {
858
+ onClick: handleCloseProfile,
859
+ className: "text-sm text-purple-200 hover:text-white transition-colors",
860
+ children: "\u2190 Back"
861
+ }
862
+ ),
863
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-semibold text-sm", children: "Profile" }),
864
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-12" }),
865
+ " "
866
+ ] }),
867
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 overflow-y-auto p-4", children: profileSaved ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-center py-8", children: [
868
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-4xl", children: "\u2705" }),
869
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-gray-600 mt-2 font-medium", children: "Profile saved!" })
870
+ ] }) : profileEditing ? (
871
+ /* Edit Profile Form */
872
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
873
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [
874
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "font-semibold text-gray-900", children: "Edit Profile" }),
875
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
876
+ "button",
877
+ {
878
+ onClick: handleCancelEditProfile,
879
+ className: "text-sm text-gray-500 hover:text-gray-700",
880
+ children: "Cancel"
881
+ }
882
+ )
883
+ ] }),
884
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-4", children: [
885
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Name" }),
886
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
887
+ "input",
888
+ {
889
+ type: "text",
890
+ value: profileName,
891
+ onChange: (e) => setProfileName(e.target.value),
892
+ placeholder: "Your name",
893
+ className: "w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
894
+ }
895
+ )
896
+ ] }),
897
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-4", children: [
898
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Primary Email" }),
899
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "px-3 py-2 bg-gray-100 rounded-lg", children: [
900
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-sm text-gray-700", children: testerInfo?.email }),
901
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-gray-400 mt-0.5", children: "Main communication email" })
902
+ ] })
903
+ ] }),
904
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-4", children: [
905
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Additional Testing Emails" }),
906
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-gray-500 mb-2", children: "Add other emails you use to test on different accounts" }),
907
+ profileAdditionalEmails.map((email) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 mb-2", children: [
908
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "flex-1 px-3 py-1.5 bg-purple-50 text-purple-700 text-sm rounded-full", children: email }),
909
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
910
+ "button",
911
+ {
912
+ onClick: () => handleRemoveEmail(email),
913
+ className: "text-purple-400 hover:text-red-500 text-sm",
914
+ children: "\u2715"
915
+ }
916
+ )
917
+ ] }, email)),
918
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
919
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
920
+ "input",
921
+ {
922
+ type: "email",
923
+ value: newEmailInput,
924
+ onChange: (e) => setNewEmailInput(e.target.value),
925
+ placeholder: "email@example.com",
926
+ className: "flex-1 px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500",
927
+ onKeyDown: (e) => e.key === "Enter" && handleAddEmail()
928
+ }
929
+ ),
930
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
931
+ "button",
932
+ {
933
+ onClick: handleAddEmail,
934
+ disabled: !newEmailInput.trim(),
935
+ className: "px-3 py-2 bg-purple-600 text-white text-sm rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed",
936
+ children: "Add"
937
+ }
938
+ )
939
+ ] })
940
+ ] }),
941
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-4", children: [
942
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Testing Platforms" }),
943
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-gray-500 mb-2", children: "Select the platforms you can test on" }),
944
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex gap-2", children: [
945
+ { key: "ios", label: "\u{1F4F1} iOS" },
946
+ { key: "android", label: "\u{1F916} Android" },
947
+ { key: "web", label: "\u{1F310} Web" }
948
+ ].map(({ key, label }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
949
+ "button",
950
+ {
951
+ onClick: () => handleTogglePlatform(key),
952
+ className: `flex-1 py-2 px-3 rounded-lg text-sm font-medium transition-colors border-2 ${profilePlatforms.includes(key) ? "bg-purple-50 border-purple-500 text-purple-700" : "bg-gray-50 border-transparent text-gray-600 hover:bg-gray-100"}`,
953
+ children: label
954
+ },
955
+ key
956
+ )) })
957
+ ] }),
958
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
959
+ "button",
960
+ {
961
+ onClick: handleSaveProfile,
962
+ disabled: savingProfile,
963
+ className: "w-full py-2 px-4 bg-green-600 text-white rounded-lg font-medium text-sm hover:bg-green-700 disabled:opacity-50 transition-colors",
964
+ children: savingProfile ? "Saving..." : "Save Profile"
965
+ }
966
+ )
967
+ ] })
968
+ ) : (
969
+ /* Profile View */
970
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
971
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "bg-gray-50 rounded-lg p-4 text-center mb-4", children: [
972
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-16 h-16 mx-auto bg-purple-600 rounded-full flex items-center justify-center mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-2xl font-semibold text-white", children: testerInfo?.name?.charAt(0)?.toUpperCase() || "?" }) }),
973
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "font-semibold text-gray-900", children: testerInfo?.name }),
974
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-sm text-gray-500", children: testerInfo?.email }),
975
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-center gap-6 mt-4 pt-4 border-t border-gray-200", children: [
976
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-center", children: [
977
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xl font-bold text-purple-600", children: testerInfo?.assignedTests || 0 }),
978
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-gray-500", children: "Assigned" })
979
+ ] }),
980
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-center", children: [
981
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xl font-bold text-purple-600", children: testerInfo?.completedTests || 0 }),
982
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-gray-500", children: "Completed" })
983
+ ] })
984
+ ] })
985
+ ] }),
986
+ (testerInfo?.additionalEmails?.length || 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "bg-gray-50 rounded-lg p-3 mb-3", children: [
987
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide mb-2", children: "Additional Emails" }),
988
+ testerInfo?.additionalEmails?.map((email) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-sm text-gray-700", children: email }, email))
989
+ ] }),
990
+ (testerInfo?.platforms?.length || 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "bg-gray-50 rounded-lg p-3 mb-3", children: [
991
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide mb-2", children: "Testing Platforms" }),
992
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-wrap gap-2", children: testerInfo?.platforms?.map((platform) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
993
+ "span",
994
+ {
995
+ className: "px-2 py-1 bg-purple-100 text-purple-700 text-xs rounded-full font-medium",
996
+ children: platform === "ios" ? "\u{1F4F1} iOS" : platform === "android" ? "\u{1F916} Android" : "\u{1F310} Web"
997
+ },
998
+ platform
999
+ )) })
1000
+ ] }),
1001
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1002
+ "button",
1003
+ {
1004
+ onClick: handleStartEditProfile,
1005
+ className: "w-full py-2 px-4 bg-purple-600 text-white rounded-lg font-medium text-sm hover:bg-purple-700 transition-colors",
1006
+ children: "Edit Profile"
1007
+ }
1008
+ )
1009
+ ] })
1010
+ ) })
1011
+ ] }),
755
1012
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "px-4 py-2 bg-gray-50 border-t border-gray-200 flex items-center justify-between text-xs text-gray-400", children: [
756
1013
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
757
1014
  pendingCount,
package/dist/index.mjs CHANGED
@@ -17,6 +17,9 @@ var BugBearContext = createContext({
17
17
  isLoading: true,
18
18
  onNavigate: void 0,
19
19
  refreshTesterStatus: async () => {
20
+ },
21
+ updateTesterProfile: async () => ({ success: false }),
22
+ refreshTesterInfo: async () => {
20
23
  }
21
24
  });
22
25
  function useBugBear() {
@@ -61,6 +64,20 @@ function BugBearProvider({ config, children, enabled = true }) {
61
64
  setClient(freshClient);
62
65
  await initializeBugBear(freshClient);
63
66
  }, [config, initializeBugBear]);
67
+ const updateTesterProfile = useCallback(async (updates) => {
68
+ if (!client) return { success: false, error: "Client not initialized" };
69
+ const result = await client.updateTesterProfile(updates);
70
+ if (result.success) {
71
+ const info = await client.getTesterInfo();
72
+ setTesterInfo(info);
73
+ }
74
+ return result;
75
+ }, [client]);
76
+ const refreshTesterInfo = useCallback(async () => {
77
+ if (!client) return;
78
+ const info = await client.getTesterInfo();
79
+ setTesterInfo(info);
80
+ }, [client]);
64
81
  useEffect(() => {
65
82
  if (enabled && !hasInitialized.current) {
66
83
  hasInitialized.current = true;
@@ -87,7 +104,9 @@ function BugBearProvider({ config, children, enabled = true }) {
87
104
  refreshAssignments,
88
105
  isLoading,
89
106
  onNavigate: config.onNavigate,
90
- refreshTesterStatus
107
+ refreshTesterStatus,
108
+ updateTesterProfile,
109
+ refreshTesterInfo
91
110
  },
92
111
  children
93
112
  }
@@ -166,7 +185,7 @@ function BugBearPanel({
166
185
  defaultCollapsed = false,
167
186
  draggable = true
168
187
  }) {
169
- const { client, shouldShowWidget, testerInfo, assignments, currentAssignment, refreshAssignments, isLoading, onNavigate } = useBugBear();
188
+ const { client, shouldShowWidget, testerInfo, assignments, currentAssignment, refreshAssignments, isLoading, onNavigate, updateTesterProfile, refreshTesterInfo } = useBugBear();
170
189
  const [collapsed, setCollapsed] = useState2(defaultCollapsed);
171
190
  const [activeTab, setActiveTab] = useState2("tests");
172
191
  const [showSteps, setShowSteps] = useState2(false);
@@ -184,6 +203,14 @@ function BugBearPanel({
184
203
  const [submitted, setSubmitted] = useState2(false);
185
204
  const [justPassed, setJustPassed] = useState2(false);
186
205
  const [criteriaResults, setCriteriaResults] = useState2({});
206
+ const [profileEditing, setProfileEditing] = useState2(false);
207
+ const [profileName, setProfileName] = useState2("");
208
+ const [profileAdditionalEmails, setProfileAdditionalEmails] = useState2([]);
209
+ const [newEmailInput, setNewEmailInput] = useState2("");
210
+ const [profilePlatforms, setProfilePlatforms] = useState2([]);
211
+ const [savingProfile, setSavingProfile] = useState2(false);
212
+ const [profileSaved, setProfileSaved] = useState2(false);
213
+ const [showProfileOverlay, setShowProfileOverlay] = useState2(false);
187
214
  useEffect2(() => {
188
215
  if (typeof window === "undefined") return;
189
216
  try {
@@ -320,6 +347,67 @@ function BugBearPanel({
320
347
  };
321
348
  const pendingCount = assignments.filter((a) => a.status === "pending").length;
322
349
  const inProgressCount = assignments.filter((a) => a.status === "in_progress").length;
350
+ const handleOpenProfile = () => {
351
+ if (testerInfo) {
352
+ setProfileName(testerInfo.name);
353
+ setProfileAdditionalEmails(testerInfo.additionalEmails || []);
354
+ setProfilePlatforms(testerInfo.platforms || []);
355
+ }
356
+ setProfileEditing(false);
357
+ setShowProfileOverlay(true);
358
+ };
359
+ const handleStartEditProfile = () => {
360
+ if (testerInfo) {
361
+ setProfileName(testerInfo.name);
362
+ setProfileAdditionalEmails(testerInfo.additionalEmails || []);
363
+ setProfilePlatforms(testerInfo.platforms || []);
364
+ }
365
+ setProfileEditing(true);
366
+ };
367
+ const handleCancelEditProfile = () => {
368
+ setProfileEditing(false);
369
+ setNewEmailInput("");
370
+ };
371
+ const handleCloseProfile = () => {
372
+ setShowProfileOverlay(false);
373
+ setProfileEditing(false);
374
+ setNewEmailInput("");
375
+ };
376
+ const handleAddEmail = () => {
377
+ const email = newEmailInput.trim().toLowerCase();
378
+ if (email && email.includes("@") && !profileAdditionalEmails.includes(email)) {
379
+ setProfileAdditionalEmails([...profileAdditionalEmails, email]);
380
+ setNewEmailInput("");
381
+ }
382
+ };
383
+ const handleRemoveEmail = (email) => {
384
+ setProfileAdditionalEmails(profileAdditionalEmails.filter((e) => e !== email));
385
+ };
386
+ const handleTogglePlatform = (platform) => {
387
+ if (profilePlatforms.includes(platform)) {
388
+ setProfilePlatforms(profilePlatforms.filter((p) => p !== platform));
389
+ } else {
390
+ setProfilePlatforms([...profilePlatforms, platform]);
391
+ }
392
+ };
393
+ const handleSaveProfile = async () => {
394
+ setSavingProfile(true);
395
+ const updates = {
396
+ name: profileName.trim(),
397
+ additionalEmails: profileAdditionalEmails,
398
+ platforms: profilePlatforms
399
+ };
400
+ const result = await updateTesterProfile(updates);
401
+ if (result.success) {
402
+ setProfileEditing(false);
403
+ setProfileSaved(true);
404
+ setTimeout(() => {
405
+ setProfileSaved(false);
406
+ setShowProfileOverlay(false);
407
+ }, 1500);
408
+ }
409
+ setSavingProfile(false);
410
+ };
323
411
  return /* @__PURE__ */ jsxs(
324
412
  "div",
325
413
  {
@@ -367,7 +455,17 @@ function BugBearPanel({
367
455
  "BugBear",
368
456
  draggable && /* @__PURE__ */ jsx2("span", { className: "text-purple-300 text-xs", title: "Drag to move, double-click to reset", children: "\u22EE\u22EE" })
369
457
  ] }),
370
- /* @__PURE__ */ jsx2("p", { className: "text-purple-200 text-xs", children: testerInfo?.name })
458
+ /* @__PURE__ */ jsxs(
459
+ "button",
460
+ {
461
+ onClick: handleOpenProfile,
462
+ className: "text-purple-200 text-xs flex items-center gap-1 hover:text-white transition-colors",
463
+ children: [
464
+ testerInfo?.name,
465
+ /* @__PURE__ */ jsx2("span", { className: "text-[10px]", children: "\u270E" })
466
+ ]
467
+ }
468
+ )
371
469
  ] })
372
470
  ] }),
373
471
  /* @__PURE__ */ jsx2(
@@ -722,6 +820,165 @@ function BugBearPanel({
722
820
  )
723
821
  ] }) })
724
822
  ] }),
823
+ showProfileOverlay && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 bg-white z-50 flex flex-col rounded-xl overflow-hidden", children: [
824
+ /* @__PURE__ */ jsxs("div", { className: "bg-purple-600 text-white px-4 py-3 flex items-center justify-between", children: [
825
+ /* @__PURE__ */ jsx2(
826
+ "button",
827
+ {
828
+ onClick: handleCloseProfile,
829
+ className: "text-sm text-purple-200 hover:text-white transition-colors",
830
+ children: "\u2190 Back"
831
+ }
832
+ ),
833
+ /* @__PURE__ */ jsx2("span", { className: "font-semibold text-sm", children: "Profile" }),
834
+ /* @__PURE__ */ jsx2("div", { className: "w-12" }),
835
+ " "
836
+ ] }),
837
+ /* @__PURE__ */ jsx2("div", { className: "flex-1 overflow-y-auto p-4", children: profileSaved ? /* @__PURE__ */ jsxs("div", { className: "text-center py-8", children: [
838
+ /* @__PURE__ */ jsx2("span", { className: "text-4xl", children: "\u2705" }),
839
+ /* @__PURE__ */ jsx2("p", { className: "text-gray-600 mt-2 font-medium", children: "Profile saved!" })
840
+ ] }) : profileEditing ? (
841
+ /* Edit Profile Form */
842
+ /* @__PURE__ */ jsxs("div", { children: [
843
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
844
+ /* @__PURE__ */ jsx2("h3", { className: "font-semibold text-gray-900", children: "Edit Profile" }),
845
+ /* @__PURE__ */ jsx2(
846
+ "button",
847
+ {
848
+ onClick: handleCancelEditProfile,
849
+ className: "text-sm text-gray-500 hover:text-gray-700",
850
+ children: "Cancel"
851
+ }
852
+ )
853
+ ] }),
854
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
855
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Name" }),
856
+ /* @__PURE__ */ jsx2(
857
+ "input",
858
+ {
859
+ type: "text",
860
+ value: profileName,
861
+ onChange: (e) => setProfileName(e.target.value),
862
+ placeholder: "Your name",
863
+ className: "w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
864
+ }
865
+ )
866
+ ] }),
867
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
868
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Primary Email" }),
869
+ /* @__PURE__ */ jsxs("div", { className: "px-3 py-2 bg-gray-100 rounded-lg", children: [
870
+ /* @__PURE__ */ jsx2("p", { className: "text-sm text-gray-700", children: testerInfo?.email }),
871
+ /* @__PURE__ */ jsx2("p", { className: "text-xs text-gray-400 mt-0.5", children: "Main communication email" })
872
+ ] })
873
+ ] }),
874
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
875
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Additional Testing Emails" }),
876
+ /* @__PURE__ */ jsx2("p", { className: "text-xs text-gray-500 mb-2", children: "Add other emails you use to test on different accounts" }),
877
+ profileAdditionalEmails.map((email) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
878
+ /* @__PURE__ */ jsx2("span", { className: "flex-1 px-3 py-1.5 bg-purple-50 text-purple-700 text-sm rounded-full", children: email }),
879
+ /* @__PURE__ */ jsx2(
880
+ "button",
881
+ {
882
+ onClick: () => handleRemoveEmail(email),
883
+ className: "text-purple-400 hover:text-red-500 text-sm",
884
+ children: "\u2715"
885
+ }
886
+ )
887
+ ] }, email)),
888
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
889
+ /* @__PURE__ */ jsx2(
890
+ "input",
891
+ {
892
+ type: "email",
893
+ value: newEmailInput,
894
+ onChange: (e) => setNewEmailInput(e.target.value),
895
+ placeholder: "email@example.com",
896
+ className: "flex-1 px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500",
897
+ onKeyDown: (e) => e.key === "Enter" && handleAddEmail()
898
+ }
899
+ ),
900
+ /* @__PURE__ */ jsx2(
901
+ "button",
902
+ {
903
+ onClick: handleAddEmail,
904
+ disabled: !newEmailInput.trim(),
905
+ className: "px-3 py-2 bg-purple-600 text-white text-sm rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed",
906
+ children: "Add"
907
+ }
908
+ )
909
+ ] })
910
+ ] }),
911
+ /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
912
+ /* @__PURE__ */ jsx2("label", { className: "block text-xs font-medium text-gray-700 mb-1", children: "Testing Platforms" }),
913
+ /* @__PURE__ */ jsx2("p", { className: "text-xs text-gray-500 mb-2", children: "Select the platforms you can test on" }),
914
+ /* @__PURE__ */ jsx2("div", { className: "flex gap-2", children: [
915
+ { key: "ios", label: "\u{1F4F1} iOS" },
916
+ { key: "android", label: "\u{1F916} Android" },
917
+ { key: "web", label: "\u{1F310} Web" }
918
+ ].map(({ key, label }) => /* @__PURE__ */ jsx2(
919
+ "button",
920
+ {
921
+ onClick: () => handleTogglePlatform(key),
922
+ className: `flex-1 py-2 px-3 rounded-lg text-sm font-medium transition-colors border-2 ${profilePlatforms.includes(key) ? "bg-purple-50 border-purple-500 text-purple-700" : "bg-gray-50 border-transparent text-gray-600 hover:bg-gray-100"}`,
923
+ children: label
924
+ },
925
+ key
926
+ )) })
927
+ ] }),
928
+ /* @__PURE__ */ jsx2(
929
+ "button",
930
+ {
931
+ onClick: handleSaveProfile,
932
+ disabled: savingProfile,
933
+ className: "w-full py-2 px-4 bg-green-600 text-white rounded-lg font-medium text-sm hover:bg-green-700 disabled:opacity-50 transition-colors",
934
+ children: savingProfile ? "Saving..." : "Save Profile"
935
+ }
936
+ )
937
+ ] })
938
+ ) : (
939
+ /* Profile View */
940
+ /* @__PURE__ */ jsxs("div", { children: [
941
+ /* @__PURE__ */ jsxs("div", { className: "bg-gray-50 rounded-lg p-4 text-center mb-4", children: [
942
+ /* @__PURE__ */ jsx2("div", { className: "w-16 h-16 mx-auto bg-purple-600 rounded-full flex items-center justify-center mb-3", children: /* @__PURE__ */ jsx2("span", { className: "text-2xl font-semibold text-white", children: testerInfo?.name?.charAt(0)?.toUpperCase() || "?" }) }),
943
+ /* @__PURE__ */ jsx2("h3", { className: "font-semibold text-gray-900", children: testerInfo?.name }),
944
+ /* @__PURE__ */ jsx2("p", { className: "text-sm text-gray-500", children: testerInfo?.email }),
945
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-6 mt-4 pt-4 border-t border-gray-200", children: [
946
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
947
+ /* @__PURE__ */ jsx2("p", { className: "text-xl font-bold text-purple-600", children: testerInfo?.assignedTests || 0 }),
948
+ /* @__PURE__ */ jsx2("p", { className: "text-xs text-gray-500", children: "Assigned" })
949
+ ] }),
950
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
951
+ /* @__PURE__ */ jsx2("p", { className: "text-xl font-bold text-purple-600", children: testerInfo?.completedTests || 0 }),
952
+ /* @__PURE__ */ jsx2("p", { className: "text-xs text-gray-500", children: "Completed" })
953
+ ] })
954
+ ] })
955
+ ] }),
956
+ (testerInfo?.additionalEmails?.length || 0) > 0 && /* @__PURE__ */ jsxs("div", { className: "bg-gray-50 rounded-lg p-3 mb-3", children: [
957
+ /* @__PURE__ */ jsx2("p", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide mb-2", children: "Additional Emails" }),
958
+ testerInfo?.additionalEmails?.map((email) => /* @__PURE__ */ jsx2("p", { className: "text-sm text-gray-700", children: email }, email))
959
+ ] }),
960
+ (testerInfo?.platforms?.length || 0) > 0 && /* @__PURE__ */ jsxs("div", { className: "bg-gray-50 rounded-lg p-3 mb-3", children: [
961
+ /* @__PURE__ */ jsx2("p", { className: "text-xs font-medium text-gray-500 uppercase tracking-wide mb-2", children: "Testing Platforms" }),
962
+ /* @__PURE__ */ jsx2("div", { className: "flex flex-wrap gap-2", children: testerInfo?.platforms?.map((platform) => /* @__PURE__ */ jsx2(
963
+ "span",
964
+ {
965
+ className: "px-2 py-1 bg-purple-100 text-purple-700 text-xs rounded-full font-medium",
966
+ children: platform === "ios" ? "\u{1F4F1} iOS" : platform === "android" ? "\u{1F916} Android" : "\u{1F310} Web"
967
+ },
968
+ platform
969
+ )) })
970
+ ] }),
971
+ /* @__PURE__ */ jsx2(
972
+ "button",
973
+ {
974
+ onClick: handleStartEditProfile,
975
+ className: "w-full py-2 px-4 bg-purple-600 text-white rounded-lg font-medium text-sm hover:bg-purple-700 transition-colors",
976
+ children: "Edit Profile"
977
+ }
978
+ )
979
+ ] })
980
+ ) })
981
+ ] }),
725
982
  /* @__PURE__ */ jsxs("div", { className: "px-4 py-2 bg-gray-50 border-t border-gray-200 flex items-center justify-between text-xs text-gray-400", children: [
726
983
  /* @__PURE__ */ jsxs("span", { children: [
727
984
  pendingCount,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/react",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "BugBear React components for web apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",