@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.
- package/dist/conversations/components/ConversationRow.js +1 -1
- package/dist/conversations/components/ConversationRow.js.map +1 -1
- package/dist/conversations/components/EmptyState.js +15 -9
- package/dist/conversations/components/EmptyState.js.map +1 -1
- package/dist/conversations/components/InboxLayout.js +93 -102
- package/dist/conversations/components/InboxLayout.js.map +1 -1
- package/dist/conversations/pages/AdminConversationsLandingPage.js +109 -61
- package/dist/conversations/pages/AdminConversationsLandingPage.js.map +1 -1
- package/dist/conversations/pages/ConversationAdminPages.js +108 -73
- package/dist/conversations/pages/ConversationAdminPages.js.map +1 -1
- package/dist/conversations/pages/ConversationDetailPage.js +311 -239
- package/dist/conversations/pages/ConversationDetailPage.js.map +1 -1
- package/dist/conversations/pages/ConversationListPage.js +64 -58
- package/dist/conversations/pages/ConversationListPage.js.map +1 -1
- package/dist/conversations/pages/ConversationsSettingsPage.js +64 -56
- package/dist/conversations/pages/ConversationsSettingsPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -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
|
|
12
|
-
import { ArrowLeft as
|
|
13
|
-
import { useNavigate as
|
|
14
|
-
import { Fragment as
|
|
15
|
-
import { Button as
|
|
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 =
|
|
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__ */
|
|
42
|
+
return /* @__PURE__ */ w(D, {
|
|
43
43
|
padded: !1,
|
|
44
44
|
className: "h-full min-h-0",
|
|
45
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
63
|
+
icon: /* @__PURE__ */ C(b, { size: 24 }),
|
|
64
64
|
maxWidth: "lg",
|
|
65
|
-
headerActions: /* @__PURE__ */
|
|
65
|
+
headerActions: /* @__PURE__ */ w("div", {
|
|
66
66
|
className: "flex items-center gap-2",
|
|
67
|
-
children: [/* @__PURE__ */ T
|
|
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__ */
|
|
74
|
-
}), /* @__PURE__ */ T
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
86
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
100
|
+
children: /* @__PURE__ */ w("div", {
|
|
93
101
|
className: "flex items-start justify-between gap-4",
|
|
94
|
-
children: [/* @__PURE__ */
|
|
102
|
+
children: [/* @__PURE__ */ w("div", {
|
|
95
103
|
className: "flex items-start gap-3",
|
|
96
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ C("div", {
|
|
97
105
|
className: "p-2 rounded-lg bg-status-info-bg-subtle text-status-info-text",
|
|
98
|
-
children: /* @__PURE__ */
|
|
99
|
-
}), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
120
|
+
/* @__PURE__ */ C("option", {
|
|
113
121
|
value: "ALL",
|
|
114
122
|
children: P("conversations.allMessages", "All Messages")
|
|
115
123
|
}),
|
|
116
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ C("option", {
|
|
117
125
|
value: "MENTIONS",
|
|
118
126
|
children: P("conversations.mentionsOnly", "Mentions Only")
|
|
119
127
|
}),
|
|
120
|
-
/* @__PURE__ */
|
|
128
|
+
/* @__PURE__ */ C("option", {
|
|
121
129
|
value: "DIRECT",
|
|
122
130
|
children: P("conversations.directOnly", "Direct Only")
|
|
123
131
|
}),
|
|
124
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
144
|
+
children: /* @__PURE__ */ w(E, {
|
|
137
145
|
treatment: "glass",
|
|
138
146
|
className: "space-y-4 border-border-seam p-4",
|
|
139
|
-
children: [/* @__PURE__ */
|
|
140
|
-
icon: /* @__PURE__ */
|
|
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__ */
|
|
146
|
-
icon: /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
175
|
+
].map((e) => /* @__PURE__ */ C(s, {
|
|
168
176
|
permission: c.CONVERSATION_READ,
|
|
169
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
185
|
+
children: [/* @__PURE__ */ C(_, { size: 12 }), X ? P("conversations.exporting", "Exporting…") : e]
|
|
178
186
|
})
|
|
179
187
|
}, e))
|
|
180
188
|
}),
|
|
181
|
-
Z && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
219
|
+
return /* @__PURE__ */ C(s, {
|
|
212
220
|
permission: c.CONVERSATION_UPDATE,
|
|
213
|
-
children: /* @__PURE__ */
|
|
221
|
+
children: /* @__PURE__ */ w("div", {
|
|
214
222
|
className: "flex items-start justify-between gap-4",
|
|
215
|
-
children: [/* @__PURE__ */
|
|
223
|
+
children: [/* @__PURE__ */ w("div", {
|
|
216
224
|
className: "flex items-start gap-3",
|
|
217
|
-
children: [/* @__PURE__ */
|
|
225
|
+
children: [/* @__PURE__ */ C("div", {
|
|
218
226
|
className: "p-2 rounded-lg bg-bg-sunken text-text-secondary",
|
|
219
227
|
children: e
|
|
220
|
-
}), /* @__PURE__ */
|
|
228
|
+
}), /* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("p", {
|
|
221
229
|
className: "text-sm font-medium text-text-primary",
|
|
222
230
|
children: t
|
|
223
|
-
}), /* @__PURE__ */
|
|
231
|
+
}), /* @__PURE__ */ C("p", {
|
|
224
232
|
className: "text-xs text-text-secondary mt-0.5",
|
|
225
233
|
children: n
|
|
226
234
|
})] })]
|
|
227
|
-
}), /* @__PURE__ */
|
|
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__ */
|
|
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.
|
|
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.
|
|
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",
|