@burdenoff/microfe-conversations 2026.625.1 → 2026.625.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.
@@ -8,15 +8,15 @@ import { PermissionGuard as s } from "../components/PermissionGuard.js";
8
8
  import { CONVERSATIONS_PERMISSIONS as c } from "../constants/permissions.js";
9
9
  import { nativeImpact as l, nativeNotify as u } from "../../utils/nativeBridge.js";
10
10
  import { useExportConversation as d } from "../hooks/useExportConversation.js";
11
- import { useEffect as f, useMemo as p, useRef as m, useState as h } from "react";
12
- import { ArrowLeft as g, Bell as _, Download as v, Eye as y, Save as b, Settings as x } from "lucide-react";
13
- import { useNavigate as S } from "react-router-dom";
14
- import { Fragment as C, jsx as w, jsxs as T } from "react/jsx-runtime";
15
- import { Button as E, GlassCard as D, PageContainer as O } from "@burdenoff/fe-libs/ui";
11
+ import { useEffect as f, useMemo as ee, useRef as p, useState as m } from "react";
12
+ import { ArrowLeft as h, Bell as g, Download as _, Eye as v, Save as y, Settings as b } from "lucide-react";
13
+ import { useNavigate as x } from "react-router-dom";
14
+ import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
15
+ import { Button as T, GlassCard as E, PageContainer as D, PagePurpose as O } from "@burdenoff/fe-libs/ui";
16
16
  import { Breadcrumbs as k } from "@burdenoff/fe-libs/chrome";
17
17
  //#region src/conversations/pages/ConversationsSettingsPage.tsx
18
18
  function A() {
19
- let A = S(), { basePath: M } = e(), { activeConversationId: N } = i(), P = t(), { conversation: F, loading: I } = a(N ?? ""), { updateConversation: L, loading: R, error: z } = o(), [B, V] = h(!0), [H, U] = h(!0), [W, G] = h("ALL"), [K, q] = h(!1), J = m(null), { exportComments: Y, loading: X, downloadUrl: Z } = d();
19
+ let A = x(), { basePath: M } = e(), { activeConversationId: N } = i(), P = t(), { conversation: F, loading: I } = a(N ?? ""), { updateConversation: L, loading: R, error: z } = o(), [B, V] = m(!0), [H, U] = m(!0), [W, G] = m("ALL"), [K, q] = m(!1), J = p(null), { exportComments: Y, loading: X, downloadUrl: Z } = d();
20
20
  f(() => {
21
21
  let e = F?.settings && typeof F.settings == "object" ? F.settings : null;
22
22
  e && (typeof e.showTypingIndicators == "boolean" && V(e.showTypingIndicators), typeof e.showReadReceipts == "boolean" && U(e.showReadReceipts), typeof e.defaultNotificationLevel == "string" && G(e.defaultNotificationLevel));
@@ -39,12 +39,12 @@ function A() {
39
39
  }
40
40
  }
41
41
  }, $ = M || "/conversations";
42
- return /* @__PURE__ */ T(O, {
42
+ return /* @__PURE__ */ w(D, {
43
43
  padded: !1,
44
44
  className: "h-full min-h-0",
45
- children: [/* @__PURE__ */ w("div", {
45
+ children: [/* @__PURE__ */ C("div", {
46
46
  className: "border-b border-border-subtle bg-bg-surface px-4 py-3 sm:px-6",
47
- children: /* @__PURE__ */ w(k, { items: p(() => [{
47
+ children: /* @__PURE__ */ C(k, { items: ee(() => [{
48
48
  label: P("conversations.title", "Conversations"),
49
49
  href: $,
50
50
  onClick: () => A($)
@@ -56,72 +56,80 @@ function A() {
56
56
  A,
57
57
  P
58
58
  ]) })
59
- }), /* @__PURE__ */ w(r, {
59
+ }), /* @__PURE__ */ C(r, {
60
60
  dataTour: "conversation-settings-page",
61
61
  title: P("conversations.settings", "Settings"),
62
62
  subtitle: F ? `${P("conversations.settingsFor", "Conversation settings for")} ${F.title}` : P("conversations.selectConversationForSettings", "Select a conversation to edit settings"),
63
- icon: /* @__PURE__ */ w(x, { size: 24 }),
63
+ icon: /* @__PURE__ */ C(b, { size: 24 }),
64
64
  maxWidth: "lg",
65
- headerActions: /* @__PURE__ */ T("div", {
65
+ headerActions: /* @__PURE__ */ w("div", {
66
66
  className: "flex items-center gap-2",
67
- children: [/* @__PURE__ */ T(E, {
67
+ children: [/* @__PURE__ */ w(T, {
68
68
  type: "button",
69
69
  variant: "ghost",
70
70
  size: "sm",
71
71
  onClick: () => A($),
72
72
  "aria-label": P("conversations.backToInbox", "Back to conversations"),
73
- children: [/* @__PURE__ */ w(g, { size: 14 }), P("conversations.back", "Back")]
74
- }), /* @__PURE__ */ T(E, {
73
+ children: [/* @__PURE__ */ C(h, { size: 14 }), P("conversations.back", "Back")]
74
+ }), /* @__PURE__ */ w(T, {
75
75
  type: "button",
76
76
  onClick: () => void Q(),
77
77
  disabled: R || !F,
78
78
  size: "sm",
79
- children: [/* @__PURE__ */ w(b, { size: 14 }), R ? P("conversations.saving", "Saving...") : P("conversations.saveChanges", "Save Changes")]
79
+ children: [/* @__PURE__ */ C(y, { size: 14 }), R ? P("conversations.saving", "Saving...") : P("conversations.saveChanges", "Save Changes")]
80
80
  })]
81
81
  }),
82
- children: I ? /* @__PURE__ */ w("div", {
82
+ children: I ? /* @__PURE__ */ C("div", {
83
83
  className: "text-sm text-text-secondary",
84
84
  children: P("conversations.loadingSettings", "Loading settings...")
85
- }) : F ? /* @__PURE__ */ T(C, { children: [
86
- /* @__PURE__ */ w(n, {
85
+ }) : F ? /* @__PURE__ */ w(S, { children: [
86
+ /* @__PURE__ */ C(O, {
87
+ icon: /* @__PURE__ */ C(b, {
88
+ size: 16,
89
+ "aria-hidden": "true"
90
+ }),
91
+ children: P("conversations.settingsPurpose", "Fine-tune how this conversation behaves for you — choose how loudly it notifies you, whether typing and read receipts are shown, and export the full message history for records or compliance.")
92
+ }),
93
+ /* @__PURE__ */ C(n, {
87
94
  title: P("conversations.notifications", "Notifications"),
88
95
  description: P("conversations.notificationsDescription", "Configure delivery behavior for this conversation"),
89
- children: /* @__PURE__ */ w(D, {
96
+ children: /* @__PURE__ */ C(E, {
90
97
  treatment: "glass",
98
+ glow: !0,
91
99
  className: "space-y-4 border-border-seam p-4",
92
- children: /* @__PURE__ */ T("div", {
100
+ children: /* @__PURE__ */ w("div", {
93
101
  className: "flex items-start justify-between gap-4",
94
- children: [/* @__PURE__ */ T("div", {
102
+ children: [/* @__PURE__ */ w("div", {
95
103
  className: "flex items-start gap-3",
96
- children: [/* @__PURE__ */ w("div", {
104
+ children: [/* @__PURE__ */ C("div", {
97
105
  className: "p-2 rounded-lg bg-status-info-bg-subtle text-status-info-text",
98
- children: /* @__PURE__ */ w(_, { size: 16 })
99
- }), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("p", {
106
+ children: /* @__PURE__ */ C(g, { size: 16 })
107
+ }), /* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("p", {
100
108
  className: "text-sm font-medium text-text-primary",
101
109
  children: P("conversations.defaultNotificationLevel", "Default Notification Level")
102
- }), /* @__PURE__ */ w("p", {
110
+ }), /* @__PURE__ */ C("p", {
103
111
  className: "text-xs text-text-secondary mt-0.5",
104
112
  children: P("conversations.defaultNotificationLevelHint", "This updates conversation settings JSON via GraphQL.")
105
113
  })] })]
106
- }), /* @__PURE__ */ T("select", {
114
+ }), /* @__PURE__ */ w("select", {
107
115
  value: W,
108
116
  onChange: (e) => G(e.target.value),
109
117
  "aria-label": P("conversations.defaultNotificationLevel", "Default Notification Level"),
110
118
  className: "min-h-11 px-3 py-1.5 text-sm rounded-lg border border-border-seam bg-bg-surface focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
111
119
  children: [
112
- /* @__PURE__ */ w("option", {
120
+ /* @__PURE__ */ C("option", {
113
121
  value: "ALL",
114
122
  children: P("conversations.allMessages", "All Messages")
115
123
  }),
116
- /* @__PURE__ */ w("option", {
124
+ /* @__PURE__ */ C("option", {
117
125
  value: "MENTIONS",
118
126
  children: P("conversations.mentionsOnly", "Mentions Only")
119
127
  }),
120
- /* @__PURE__ */ w("option", {
128
+ /* @__PURE__ */ C("option", {
121
129
  value: "DIRECT",
122
130
  children: P("conversations.directOnly", "Direct Only")
123
131
  }),
124
- /* @__PURE__ */ w("option", {
132
+ /* @__PURE__ */ C("option", {
125
133
  value: "NONE",
126
134
  children: P("conversations.none", "None")
127
135
  })
@@ -130,20 +138,20 @@ function A() {
130
138
  })
131
139
  })
132
140
  }),
133
- /* @__PURE__ */ w(n, {
141
+ /* @__PURE__ */ C(n, {
134
142
  title: P("conversations.messageBehavior", "Message Behavior"),
135
143
  description: P("conversations.messageBehaviorDescription", "Tune read and typing experience"),
136
- children: /* @__PURE__ */ T(D, {
144
+ children: /* @__PURE__ */ w(E, {
137
145
  treatment: "glass",
138
146
  className: "space-y-4 border-border-seam p-4",
139
- children: [/* @__PURE__ */ w(j, {
140
- icon: /* @__PURE__ */ w(y, { size: 16 }),
147
+ children: [/* @__PURE__ */ C(j, {
148
+ icon: /* @__PURE__ */ C(v, { size: 16 }),
141
149
  label: P("conversations.typingIndicators", "Typing Indicators"),
142
150
  description: P("conversations.typingIndicatorsDescription", "Show others when someone is typing"),
143
151
  checked: B,
144
152
  onChange: V
145
- }), /* @__PURE__ */ w(j, {
146
- icon: /* @__PURE__ */ w(y, { size: 16 }),
153
+ }), /* @__PURE__ */ C(j, {
154
+ icon: /* @__PURE__ */ C(v, { size: 16 }),
147
155
  label: P("conversations.readReceipts", "Read Receipts"),
148
156
  description: P("conversations.readReceiptsDescription", "Display read status for conversation participants"),
149
157
  checked: H,
@@ -151,22 +159,22 @@ function A() {
151
159
  })]
152
160
  })
153
161
  }),
154
- /* @__PURE__ */ w(n, {
162
+ /* @__PURE__ */ C(n, {
155
163
  title: P("conversations.exportData", "Export Data"),
156
164
  description: P("conversations.exportDataDescription", "Download conversation comments in your preferred format"),
157
- children: /* @__PURE__ */ T(D, {
165
+ children: /* @__PURE__ */ w(E, {
158
166
  treatment: "glass",
159
167
  className: "space-y-3 border-border-seam p-4",
160
168
  children: [
161
- /* @__PURE__ */ w("div", {
169
+ /* @__PURE__ */ C("div", {
162
170
  className: "flex flex-wrap gap-2",
163
171
  children: [
164
172
  "JSON",
165
173
  "CSV",
166
174
  "MARKDOWN"
167
- ].map((e) => /* @__PURE__ */ w(s, {
175
+ ].map((e) => /* @__PURE__ */ C(s, {
168
176
  permission: c.CONVERSATION_READ,
169
- children: /* @__PURE__ */ T("button", {
177
+ children: /* @__PURE__ */ w("button", {
170
178
  type: "button",
171
179
  disabled: X || !F,
172
180
  onClick: async () => {
@@ -174,15 +182,15 @@ function A() {
174
182
  t && J.current && (J.current.href = t, J.current.download = `conversation-${F.id}.${e.toLowerCase()}`, J.current.click());
175
183
  },
176
184
  className: "inline-flex min-h-11 items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-border-seam text-text-secondary hover:text-text-primary hover:bg-bg-sunken/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-50",
177
- children: [/* @__PURE__ */ w(v, { size: 12 }), X ? P("conversations.exporting", "Exporting…") : e]
185
+ children: [/* @__PURE__ */ C(_, { size: 12 }), X ? P("conversations.exporting", "Exporting…") : e]
178
186
  })
179
187
  }, e))
180
188
  }),
181
- Z && /* @__PURE__ */ w("p", {
189
+ Z && /* @__PURE__ */ C("p", {
182
190
  className: "text-xs text-status-success-text",
183
191
  children: P("conversations.exportReady", "Export ready — downloading…")
184
192
  }),
185
- /* @__PURE__ */ w("a", {
193
+ /* @__PURE__ */ C("a", {
186
194
  ref: J,
187
195
  href: "#download",
188
196
  tabIndex: -1,
@@ -192,15 +200,15 @@ function A() {
192
200
  ]
193
201
  })
194
202
  }),
195
- z && /* @__PURE__ */ w("div", {
203
+ z && /* @__PURE__ */ C("div", {
196
204
  className: "text-xs text-status-error-text rounded-lg border border-status-error-border/30 bg-status-error-bg/10 px-3 py-2",
197
205
  children: z.message
198
206
  }),
199
- K && !z && /* @__PURE__ */ w("div", {
207
+ K && !z && /* @__PURE__ */ C("div", {
200
208
  className: "text-xs text-status-success-text rounded-lg border border-border-default bg-status-success-bg-subtle px-3 py-2",
201
209
  children: P("conversations.settingsSaved", "Settings saved.")
202
210
  })
203
- ] }) : /* @__PURE__ */ w("div", {
211
+ ] }) : /* @__PURE__ */ C("div", {
204
212
  className: "text-sm text-text-secondary",
205
213
  children: P("conversations.openConversationFirst", "Open a conversation first, then come back to settings.")
206
214
  })
@@ -208,30 +216,30 @@ function A() {
208
216
  });
209
217
  }
210
218
  function j({ icon: e, label: t, description: n, checked: r, onChange: i }) {
211
- return /* @__PURE__ */ w(s, {
219
+ return /* @__PURE__ */ C(s, {
212
220
  permission: c.CONVERSATION_UPDATE,
213
- children: /* @__PURE__ */ T("div", {
221
+ children: /* @__PURE__ */ w("div", {
214
222
  className: "flex items-start justify-between gap-4",
215
- children: [/* @__PURE__ */ T("div", {
223
+ children: [/* @__PURE__ */ w("div", {
216
224
  className: "flex items-start gap-3",
217
- children: [/* @__PURE__ */ w("div", {
225
+ children: [/* @__PURE__ */ C("div", {
218
226
  className: "p-2 rounded-lg bg-bg-sunken text-text-secondary",
219
227
  children: e
220
- }), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("p", {
228
+ }), /* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("p", {
221
229
  className: "text-sm font-medium text-text-primary",
222
230
  children: t
223
- }), /* @__PURE__ */ w("p", {
231
+ }), /* @__PURE__ */ C("p", {
224
232
  className: "text-xs text-text-secondary mt-0.5",
225
233
  children: n
226
234
  })] })]
227
- }), /* @__PURE__ */ w("button", {
235
+ }), /* @__PURE__ */ C("button", {
228
236
  type: "button",
229
237
  role: "switch",
230
238
  "aria-checked": r,
231
239
  "aria-label": t,
232
240
  onClick: () => i(!r),
233
241
  className: `relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${r ? "bg-action-primary-bg" : "bg-bg-sunken"}`,
234
- children: /* @__PURE__ */ w("span", { className: `pointer-events-none inline-block h-5 w-5 rounded-full bg-bg-surface shadow transition ${r ? "translate-x-5" : "translate-x-0"}` })
242
+ children: /* @__PURE__ */ C("span", { className: `pointer-events-none inline-block h-5 w-5 rounded-full bg-bg-surface shadow transition ${r ? "translate-x-5" : "translate-x-0"}` })
235
243
  })]
236
244
  })
237
245
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationsSettingsPage.js","names":[],"sources":["../../../src/conversations/pages/ConversationsSettingsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport { ArrowLeft, Bell, Download, Eye, Save, Settings } from \"lucide-react\";\nimport { Breadcrumbs, type BreadcrumbItem } from \"@burdenoff/fe-libs/chrome\";\nimport { Button, GlassCard, PageContainer } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n useGetConversation,\n useUpdateConversation,\n} from \"../hooks/useConversationQueries\";\nimport { useExportConversation } from \"../hooks/useExportConversation\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { useConversationsStore } from \"../store/conversationsStore\";\nimport { PermissionGuard } from \"../components/PermissionGuard\";\nimport { CONVERSATIONS_PERMISSIONS } from \"../constants/permissions\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport { useTr } from \"../utils/i18n\";\n\nexport function ConversationsSettingsPage() {\n const navigate = useNavigate();\n const { basePath } = useConversations();\n const { activeConversationId } = useConversationsStore();\n const tr = useTr();\n const { conversation, loading: conversationLoading } = useGetConversation(\n activeConversationId ?? \"\",\n );\n const {\n updateConversation,\n loading: saving,\n error: saveError,\n } = useUpdateConversation();\n\n const [showTypingIndicators, setShowTypingIndicators] = useState(true);\n const [showReadReceipts, setShowReadReceipts] = useState(true);\n const [defaultNotificationLevel, setDefaultNotificationLevel] =\n useState(\"ALL\");\n const [saved, setSaved] = useState(false);\n const downloadLinkRef = useRef<HTMLAnchorElement>(null);\n const {\n exportComments,\n loading: exporting,\n downloadUrl,\n } = useExportConversation();\n\n useEffect(() => {\n const settings =\n conversation?.settings && typeof conversation.settings === \"object\"\n ? (conversation.settings as Record<string, unknown>)\n : null;\n if (!settings) return;\n if (typeof settings.showTypingIndicators === \"boolean\") {\n setShowTypingIndicators(settings.showTypingIndicators);\n }\n if (typeof settings.showReadReceipts === \"boolean\") {\n setShowReadReceipts(settings.showReadReceipts);\n }\n if (typeof settings.defaultNotificationLevel === \"string\") {\n setDefaultNotificationLevel(settings.defaultNotificationLevel);\n }\n }, [conversation]);\n\n const handleSave = async () => {\n if (!conversation?.id) return;\n nativeImpact(\"light\");\n try {\n const updated = await updateConversation({\n id: conversation.id,\n settings: {\n showTypingIndicators,\n showReadReceipts,\n defaultNotificationLevel,\n },\n });\n const ok = Boolean(updated);\n nativeNotify(ok ? \"success\" : \"error\");\n setSaved(ok);\n } catch (error) {\n nativeNotify(\"error\");\n throw error;\n }\n };\n\n const inboxHref = basePath || \"/conversations\";\n const breadcrumbItems = useMemo<BreadcrumbItem[]>(\n () => [\n {\n label: tr(\"conversations.title\", \"Conversations\"),\n href: inboxHref,\n onClick: () => navigate(inboxHref),\n },\n {\n label: tr(\"conversations.settings\", \"Settings\"),\n isCurrentPage: true,\n },\n ],\n [inboxHref, navigate, tr],\n );\n\n return (\n <PageContainer padded={false} className=\"h-full min-h-0\">\n <div className=\"border-b border-border-subtle bg-bg-surface px-4 py-3 sm:px-6\">\n <Breadcrumbs items={breadcrumbItems} />\n </div>\n <PageLayout\n dataTour=\"conversation-settings-page\"\n title={tr(\"conversations.settings\", \"Settings\")}\n subtitle={\n conversation\n ? `${tr(\"conversations.settingsFor\", \"Conversation settings for\")} ${conversation.title}`\n : tr(\n \"conversations.selectConversationForSettings\",\n \"Select a conversation to edit settings\",\n )\n }\n icon={<Settings size={24} />}\n maxWidth=\"lg\"\n headerActions={\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => navigate(inboxHref)}\n aria-label={tr(\n \"conversations.backToInbox\",\n \"Back to conversations\",\n )}\n >\n <ArrowLeft size={14} />\n {tr(\"conversations.back\", \"Back\")}\n </Button>\n <Button\n type=\"button\"\n onClick={() => void handleSave()}\n disabled={saving || !conversation}\n size=\"sm\"\n >\n <Save size={14} />\n {saving\n ? tr(\"conversations.saving\", \"Saving...\")\n : tr(\"conversations.saveChanges\", \"Save Changes\")}\n </Button>\n </div>\n }\n >\n {conversationLoading ? (\n <div className=\"text-sm text-text-secondary\">\n {tr(\"conversations.loadingSettings\", \"Loading settings...\")}\n </div>\n ) : !conversation ? (\n <div className=\"text-sm text-text-secondary\">\n {tr(\n \"conversations.openConversationFirst\",\n \"Open a conversation first, then come back to settings.\",\n )}\n </div>\n ) : (\n <>\n <PageSection\n title={tr(\"conversations.notifications\", \"Notifications\")}\n description={tr(\n \"conversations.notificationsDescription\",\n \"Configure delivery behavior for this conversation\",\n )}\n >\n <GlassCard\n treatment=\"glass\"\n className=\"space-y-4 border-border-seam p-4\"\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"p-2 rounded-lg bg-status-info-bg-subtle text-status-info-text\">\n <Bell size={16} />\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\n \"conversations.defaultNotificationLevel\",\n \"Default Notification Level\",\n )}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {tr(\n \"conversations.defaultNotificationLevelHint\",\n \"This updates conversation settings JSON via GraphQL.\",\n )}\n </p>\n </div>\n </div>\n <select\n value={defaultNotificationLevel}\n onChange={(event) =>\n setDefaultNotificationLevel(event.target.value)\n }\n aria-label={tr(\n \"conversations.defaultNotificationLevel\",\n \"Default Notification Level\",\n )}\n className=\"min-h-11 px-3 py-1.5 text-sm rounded-lg border border-border-seam bg-bg-surface focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"ALL\">\n {tr(\"conversations.allMessages\", \"All Messages\")}\n </option>\n <option value=\"MENTIONS\">\n {tr(\"conversations.mentionsOnly\", \"Mentions Only\")}\n </option>\n <option value=\"DIRECT\">\n {tr(\"conversations.directOnly\", \"Direct Only\")}\n </option>\n <option value=\"NONE\">\n {tr(\"conversations.none\", \"None\")}\n </option>\n </select>\n </div>\n </GlassCard>\n </PageSection>\n\n <PageSection\n title={tr(\"conversations.messageBehavior\", \"Message Behavior\")}\n description={tr(\n \"conversations.messageBehaviorDescription\",\n \"Tune read and typing experience\",\n )}\n >\n <GlassCard\n treatment=\"glass\"\n className=\"space-y-4 border-border-seam p-4\"\n >\n <ToggleSetting\n icon={<Eye size={16} />}\n label={tr(\n \"conversations.typingIndicators\",\n \"Typing Indicators\",\n )}\n description={tr(\n \"conversations.typingIndicatorsDescription\",\n \"Show others when someone is typing\",\n )}\n checked={showTypingIndicators}\n onChange={setShowTypingIndicators}\n />\n <ToggleSetting\n icon={<Eye size={16} />}\n label={tr(\"conversations.readReceipts\", \"Read Receipts\")}\n description={tr(\n \"conversations.readReceiptsDescription\",\n \"Display read status for conversation participants\",\n )}\n checked={showReadReceipts}\n onChange={setShowReadReceipts}\n />\n </GlassCard>\n </PageSection>\n\n <PageSection\n title={tr(\"conversations.exportData\", \"Export Data\")}\n description={tr(\n \"conversations.exportDataDescription\",\n \"Download conversation comments in your preferred format\",\n )}\n >\n <GlassCard\n treatment=\"glass\"\n className=\"space-y-3 border-border-seam p-4\"\n >\n <div className=\"flex flex-wrap gap-2\">\n {([\"JSON\", \"CSV\", \"MARKDOWN\"] as const).map((format) => (\n <PermissionGuard\n key={format}\n permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_READ}\n >\n <button\n type=\"button\"\n disabled={exporting || !conversation}\n onClick={async () => {\n const url = await exportComments(\n \"conversation\",\n conversation!.id,\n format,\n );\n if (url && downloadLinkRef.current) {\n downloadLinkRef.current.href = url;\n downloadLinkRef.current.download = `conversation-${conversation!.id}.${format.toLowerCase()}`;\n downloadLinkRef.current.click();\n }\n }}\n className=\"inline-flex min-h-11 items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-border-seam text-text-secondary hover:text-text-primary hover:bg-bg-sunken/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n >\n <Download size={12} />\n {exporting\n ? tr(\"conversations.exporting\", \"Exporting…\")\n : format}\n </button>\n </PermissionGuard>\n ))}\n </div>\n {downloadUrl && (\n <p className=\"text-xs text-status-success-text\">\n {tr(\n \"conversations.exportReady\",\n \"Export ready — downloading…\",\n )}\n </p>\n )}\n {/* Hidden anchor driven programmatically for download trigger.\n href is set imperatively before .click() — placeholder satisfies\n anchor-is-valid; tabIndex=-1 keeps it out of focus order. */}\n <a\n ref={downloadLinkRef}\n href=\"#download\"\n tabIndex={-1}\n className=\"hidden\"\n aria-hidden=\"true\"\n />\n </GlassCard>\n </PageSection>\n\n {saveError && (\n <div className=\"text-xs text-status-error-text rounded-lg border border-status-error-border/30 bg-status-error-bg/10 px-3 py-2\">\n {saveError.message}\n </div>\n )}\n {saved && !saveError && (\n <div className=\"text-xs text-status-success-text rounded-lg border border-border-default bg-status-success-bg-subtle px-3 py-2\">\n {tr(\"conversations.settingsSaved\", \"Settings saved.\")}\n </div>\n )}\n </>\n )}\n </PageLayout>\n </PageContainer>\n );\n}\n\nfunction ToggleSetting({\n icon,\n label,\n description,\n checked,\n onChange,\n}: {\n icon: React.ReactNode;\n label: string;\n description: string;\n checked: boolean;\n onChange: (value: boolean) => void;\n}) {\n return (\n <PermissionGuard permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_UPDATE}>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"p-2 rounded-lg bg-bg-sunken text-text-secondary\">\n {icon}\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">{label}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {description}\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n aria-label={label}\n onClick={() => onChange(!checked)}\n className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${checked ? \"bg-action-primary-bg\" : \"bg-bg-sunken\"}`}\n >\n <span\n className={`pointer-events-none inline-block h-5 w-5 rounded-full bg-bg-surface shadow transition ${checked ? \"translate-x-5\" : \"translate-x-0\"}`}\n />\n </button>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ConversationsSettingsPage.js","names":[],"sources":["../../../src/conversations/pages/ConversationsSettingsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport { ArrowLeft, Bell, Download, Eye, Save, Settings } from \"lucide-react\";\nimport { Breadcrumbs, type BreadcrumbItem } from \"@burdenoff/fe-libs/chrome\";\nimport {\n Button,\n GlassCard,\n PageContainer,\n PagePurpose,\n} from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport {\n useGetConversation,\n useUpdateConversation,\n} from \"../hooks/useConversationQueries\";\nimport { useExportConversation } from \"../hooks/useExportConversation\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { useConversationsStore } from \"../store/conversationsStore\";\nimport { PermissionGuard } from \"../components/PermissionGuard\";\nimport { CONVERSATIONS_PERMISSIONS } from \"../constants/permissions\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\nimport { useTr } from \"../utils/i18n\";\n\nexport function ConversationsSettingsPage() {\n const navigate = useNavigate();\n const { basePath } = useConversations();\n const { activeConversationId } = useConversationsStore();\n const tr = useTr();\n const { conversation, loading: conversationLoading } = useGetConversation(\n activeConversationId ?? \"\",\n );\n const {\n updateConversation,\n loading: saving,\n error: saveError,\n } = useUpdateConversation();\n\n const [showTypingIndicators, setShowTypingIndicators] = useState(true);\n const [showReadReceipts, setShowReadReceipts] = useState(true);\n const [defaultNotificationLevel, setDefaultNotificationLevel] =\n useState(\"ALL\");\n const [saved, setSaved] = useState(false);\n const downloadLinkRef = useRef<HTMLAnchorElement>(null);\n const {\n exportComments,\n loading: exporting,\n downloadUrl,\n } = useExportConversation();\n\n useEffect(() => {\n const settings =\n conversation?.settings && typeof conversation.settings === \"object\"\n ? (conversation.settings as Record<string, unknown>)\n : null;\n if (!settings) return;\n if (typeof settings.showTypingIndicators === \"boolean\") {\n setShowTypingIndicators(settings.showTypingIndicators);\n }\n if (typeof settings.showReadReceipts === \"boolean\") {\n setShowReadReceipts(settings.showReadReceipts);\n }\n if (typeof settings.defaultNotificationLevel === \"string\") {\n setDefaultNotificationLevel(settings.defaultNotificationLevel);\n }\n }, [conversation]);\n\n const handleSave = async () => {\n if (!conversation?.id) return;\n nativeImpact(\"light\");\n try {\n const updated = await updateConversation({\n id: conversation.id,\n settings: {\n showTypingIndicators,\n showReadReceipts,\n defaultNotificationLevel,\n },\n });\n const ok = Boolean(updated);\n nativeNotify(ok ? \"success\" : \"error\");\n setSaved(ok);\n } catch (error) {\n nativeNotify(\"error\");\n throw error;\n }\n };\n\n const inboxHref = basePath || \"/conversations\";\n const breadcrumbItems = useMemo<BreadcrumbItem[]>(\n () => [\n {\n label: tr(\"conversations.title\", \"Conversations\"),\n href: inboxHref,\n onClick: () => navigate(inboxHref),\n },\n {\n label: tr(\"conversations.settings\", \"Settings\"),\n isCurrentPage: true,\n },\n ],\n [inboxHref, navigate, tr],\n );\n\n return (\n <PageContainer padded={false} className=\"h-full min-h-0\">\n <div className=\"border-b border-border-subtle bg-bg-surface px-4 py-3 sm:px-6\">\n <Breadcrumbs items={breadcrumbItems} />\n </div>\n <PageLayout\n dataTour=\"conversation-settings-page\"\n title={tr(\"conversations.settings\", \"Settings\")}\n subtitle={\n conversation\n ? `${tr(\"conversations.settingsFor\", \"Conversation settings for\")} ${conversation.title}`\n : tr(\n \"conversations.selectConversationForSettings\",\n \"Select a conversation to edit settings\",\n )\n }\n icon={<Settings size={24} />}\n maxWidth=\"lg\"\n headerActions={\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => navigate(inboxHref)}\n aria-label={tr(\n \"conversations.backToInbox\",\n \"Back to conversations\",\n )}\n >\n <ArrowLeft size={14} />\n {tr(\"conversations.back\", \"Back\")}\n </Button>\n <Button\n type=\"button\"\n onClick={() => void handleSave()}\n disabled={saving || !conversation}\n size=\"sm\"\n >\n <Save size={14} />\n {saving\n ? tr(\"conversations.saving\", \"Saving...\")\n : tr(\"conversations.saveChanges\", \"Save Changes\")}\n </Button>\n </div>\n }\n >\n {conversationLoading ? (\n <div className=\"text-sm text-text-secondary\">\n {tr(\"conversations.loadingSettings\", \"Loading settings...\")}\n </div>\n ) : !conversation ? (\n <div className=\"text-sm text-text-secondary\">\n {tr(\n \"conversations.openConversationFirst\",\n \"Open a conversation first, then come back to settings.\",\n )}\n </div>\n ) : (\n <>\n <PagePurpose icon={<Settings size={16} aria-hidden=\"true\" />}>\n {tr(\n \"conversations.settingsPurpose\",\n \"Fine-tune how this conversation behaves for you — choose how loudly it notifies you, whether typing and read receipts are shown, and export the full message history for records or compliance.\",\n )}\n </PagePurpose>\n\n <PageSection\n title={tr(\"conversations.notifications\", \"Notifications\")}\n description={tr(\n \"conversations.notificationsDescription\",\n \"Configure delivery behavior for this conversation\",\n )}\n >\n {/* The single emphasis zone on this page — notification level is\n the most consequential setting here. */}\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"space-y-4 border-border-seam p-4\"\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"p-2 rounded-lg bg-status-info-bg-subtle text-status-info-text\">\n <Bell size={16} />\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {tr(\n \"conversations.defaultNotificationLevel\",\n \"Default Notification Level\",\n )}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {tr(\n \"conversations.defaultNotificationLevelHint\",\n \"This updates conversation settings JSON via GraphQL.\",\n )}\n </p>\n </div>\n </div>\n <select\n value={defaultNotificationLevel}\n onChange={(event) =>\n setDefaultNotificationLevel(event.target.value)\n }\n aria-label={tr(\n \"conversations.defaultNotificationLevel\",\n \"Default Notification Level\",\n )}\n className=\"min-h-11 px-3 py-1.5 text-sm rounded-lg border border-border-seam bg-bg-surface focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"ALL\">\n {tr(\"conversations.allMessages\", \"All Messages\")}\n </option>\n <option value=\"MENTIONS\">\n {tr(\"conversations.mentionsOnly\", \"Mentions Only\")}\n </option>\n <option value=\"DIRECT\">\n {tr(\"conversations.directOnly\", \"Direct Only\")}\n </option>\n <option value=\"NONE\">\n {tr(\"conversations.none\", \"None\")}\n </option>\n </select>\n </div>\n </GlassCard>\n </PageSection>\n\n <PageSection\n title={tr(\"conversations.messageBehavior\", \"Message Behavior\")}\n description={tr(\n \"conversations.messageBehaviorDescription\",\n \"Tune read and typing experience\",\n )}\n >\n <GlassCard\n treatment=\"glass\"\n className=\"space-y-4 border-border-seam p-4\"\n >\n <ToggleSetting\n icon={<Eye size={16} />}\n label={tr(\n \"conversations.typingIndicators\",\n \"Typing Indicators\",\n )}\n description={tr(\n \"conversations.typingIndicatorsDescription\",\n \"Show others when someone is typing\",\n )}\n checked={showTypingIndicators}\n onChange={setShowTypingIndicators}\n />\n <ToggleSetting\n icon={<Eye size={16} />}\n label={tr(\"conversations.readReceipts\", \"Read Receipts\")}\n description={tr(\n \"conversations.readReceiptsDescription\",\n \"Display read status for conversation participants\",\n )}\n checked={showReadReceipts}\n onChange={setShowReadReceipts}\n />\n </GlassCard>\n </PageSection>\n\n <PageSection\n title={tr(\"conversations.exportData\", \"Export Data\")}\n description={tr(\n \"conversations.exportDataDescription\",\n \"Download conversation comments in your preferred format\",\n )}\n >\n <GlassCard\n treatment=\"glass\"\n className=\"space-y-3 border-border-seam p-4\"\n >\n <div className=\"flex flex-wrap gap-2\">\n {([\"JSON\", \"CSV\", \"MARKDOWN\"] as const).map((format) => (\n <PermissionGuard\n key={format}\n permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_READ}\n >\n <button\n type=\"button\"\n disabled={exporting || !conversation}\n onClick={async () => {\n const url = await exportComments(\n \"conversation\",\n conversation!.id,\n format,\n );\n if (url && downloadLinkRef.current) {\n downloadLinkRef.current.href = url;\n downloadLinkRef.current.download = `conversation-${conversation!.id}.${format.toLowerCase()}`;\n downloadLinkRef.current.click();\n }\n }}\n className=\"inline-flex min-h-11 items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-border-seam text-text-secondary hover:text-text-primary hover:bg-bg-sunken/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n >\n <Download size={12} />\n {exporting\n ? tr(\"conversations.exporting\", \"Exporting…\")\n : format}\n </button>\n </PermissionGuard>\n ))}\n </div>\n {downloadUrl && (\n <p className=\"text-xs text-status-success-text\">\n {tr(\n \"conversations.exportReady\",\n \"Export ready — downloading…\",\n )}\n </p>\n )}\n {/* Hidden anchor driven programmatically for download trigger.\n href is set imperatively before .click() — placeholder satisfies\n anchor-is-valid; tabIndex=-1 keeps it out of focus order. */}\n <a\n ref={downloadLinkRef}\n href=\"#download\"\n tabIndex={-1}\n className=\"hidden\"\n aria-hidden=\"true\"\n />\n </GlassCard>\n </PageSection>\n\n {saveError && (\n <div className=\"text-xs text-status-error-text rounded-lg border border-status-error-border/30 bg-status-error-bg/10 px-3 py-2\">\n {saveError.message}\n </div>\n )}\n {saved && !saveError && (\n <div className=\"text-xs text-status-success-text rounded-lg border border-border-default bg-status-success-bg-subtle px-3 py-2\">\n {tr(\"conversations.settingsSaved\", \"Settings saved.\")}\n </div>\n )}\n </>\n )}\n </PageLayout>\n </PageContainer>\n );\n}\n\nfunction ToggleSetting({\n icon,\n label,\n description,\n checked,\n onChange,\n}: {\n icon: React.ReactNode;\n label: string;\n description: string;\n checked: boolean;\n onChange: (value: boolean) => void;\n}) {\n return (\n <PermissionGuard permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_UPDATE}>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"p-2 rounded-lg bg-bg-sunken text-text-secondary\">\n {icon}\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">{label}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">{description}</p>\n </div>\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n aria-label={label}\n onClick={() => onChange(!checked)}\n className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${checked ? \"bg-action-primary-bg\" : \"bg-bg-sunken\"}`}\n >\n <span\n className={`pointer-events-none inline-block h-5 w-5 rounded-full bg-bg-surface shadow transition ${checked ? \"translate-x-5\" : \"translate-x-0\"}`}\n />\n </button>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-conversations",
3
- "version": "2026.625.1",
3
+ "version": "2026.625.3",
4
4
  "description": "Burdenoff Conversations Microfrontend - Complete messaging and conversation interface",
5
5
  "type": "module",
6
6
  "files": [
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@apollo/client": "^4.0.11",
46
- "@burdenoff/fe-libs": "2026.625.2",
46
+ "@burdenoff/fe-libs": "2026.625.5",
47
47
  "@tanstack/react-query": "^5.90.16",
48
48
  "clsx": "^2.1.1",
49
49
  "graphql": "^16.10.0",