@burdenoff/microfe-conversations 2026.930.1 → 2026.1006.1
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/NewConversationDialog.js +135 -133
- package/dist/conversations/components/NewConversationDialog.js.map +1 -1
- package/dist/conversations/hooks/useConversationQueries.js +66 -49
- package/dist/conversations/hooks/useConversationQueries.js.map +1 -1
- package/dist/generated/wspace-operations.js +87 -32
- package/dist/generated/wspace-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { useConversations as e } from "../providers/ConversationsProvider.js";
|
|
2
2
|
import { useTr as t } from "../utils/i18n.js";
|
|
3
3
|
import { cn as n } from "../utils/cn.js";
|
|
4
|
-
import { useCreateConversation as r } from "../hooks/useConversationQueries.js";
|
|
5
|
-
import { QuotaExhaustedDialog as
|
|
6
|
-
import { nativeImpact as te, nativeNotify as
|
|
7
|
-
import { MemberPicker as
|
|
4
|
+
import { useCreateConversation as ee, useStartDirectConversation as r } from "../hooks/useConversationQueries.js";
|
|
5
|
+
import { QuotaExhaustedDialog as i } from "./QuotaExhaustedDialog.js";
|
|
6
|
+
import { nativeImpact as te, nativeNotify as a } from "../../utils/nativeBridge.js";
|
|
7
|
+
import { MemberPicker as ne } from "./MemberPicker.js";
|
|
8
8
|
import { useCallback as o, useMemo as s, useState as c } from "react";
|
|
9
|
-
import { Globe as l, Hash as u, Lock as d, Megaphone as
|
|
10
|
-
import { useNavigate as
|
|
11
|
-
import { isQuotaExhaustedError as
|
|
12
|
-
import { useEventBus as
|
|
13
|
-
import { Fragment as
|
|
14
|
-
import { Dialog as
|
|
9
|
+
import { Globe as l, Hash as u, Lock as d, Megaphone as f, MessageCircle as p, Shield as re, Users as ie } from "lucide-react";
|
|
10
|
+
import { useNavigate as ae } from "react-router";
|
|
11
|
+
import { isQuotaExhaustedError as m } from "@burdenoff/fe-libs/shared/utils";
|
|
12
|
+
import { useEventBus as oe } from "@burdenoff/fe-libs/shared/events";
|
|
13
|
+
import { Fragment as se, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
14
|
+
import { Dialog as ce, DialogContent as le, DialogDescription as ue, DialogFooter as _, DialogHeader as de, DialogTitle as v } from "@burdenoff/fe-libs/ui";
|
|
15
15
|
//#region src/conversations/components/NewConversationDialog.tsx
|
|
16
|
-
function
|
|
16
|
+
function y(e) {
|
|
17
17
|
if ("graphQLErrors" in e) {
|
|
18
18
|
let t = e.graphQLErrors;
|
|
19
19
|
if (t && t.length > 0 && t[0]?.message) return t[0].message;
|
|
@@ -21,251 +21,253 @@ function ue(e) {
|
|
|
21
21
|
return e.message;
|
|
22
22
|
}
|
|
23
23
|
function b({ open: b, onOpenChange: x, onQuotaExhausted: S, onQuotaDialogClose: C, onCreated: w }) {
|
|
24
|
-
let { createConversation: T, loading: E, error:
|
|
24
|
+
let { createConversation: T, loading: fe, error: pe } = ee(), { startDirectConversation: E, loading: me, error: he } = r(), { currentUser: D } = e(), O = oe(), k = t(), A = ae(), [j, M] = c(!1), N = s(() => [
|
|
25
25
|
{
|
|
26
26
|
type: "DIRECT",
|
|
27
|
-
label:
|
|
28
|
-
description:
|
|
29
|
-
icon: /* @__PURE__ */
|
|
27
|
+
label: k("conversations.directMessage", "Direct Message"),
|
|
28
|
+
description: k("conversations.directMessageDescription", "One-on-one private conversation"),
|
|
29
|
+
icon: /* @__PURE__ */ h(p, { size: 20 })
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
type: "GROUP",
|
|
33
|
-
label:
|
|
34
|
-
description:
|
|
35
|
-
icon: /* @__PURE__ */
|
|
33
|
+
label: k("conversations.groupChat", "Group Chat"),
|
|
34
|
+
description: k("conversations.groupChatDescription", "Invite multiple people to a group conversation"),
|
|
35
|
+
icon: /* @__PURE__ */ h(ie, { size: 20 })
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
type: "CHANNEL",
|
|
39
|
-
label:
|
|
40
|
-
description:
|
|
41
|
-
icon: /* @__PURE__ */
|
|
39
|
+
label: k("conversations.channel", "Channel"),
|
|
40
|
+
description: k("conversations.channelDescription", "A topic-based channel that anyone can join"),
|
|
41
|
+
icon: /* @__PURE__ */ h(u, { size: 20 })
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
type: "ANNOUNCEMENT",
|
|
45
|
-
label:
|
|
46
|
-
description:
|
|
47
|
-
icon: /* @__PURE__ */
|
|
45
|
+
label: k("conversations.announcement", "Announcement"),
|
|
46
|
+
description: k("conversations.announcementDescription", "Broadcast messages to a large audience"),
|
|
47
|
+
icon: /* @__PURE__ */ h(f, { size: 20 })
|
|
48
48
|
}
|
|
49
|
-
], [
|
|
49
|
+
], [k]), P = s(() => [
|
|
50
50
|
{
|
|
51
51
|
value: "PUBLIC",
|
|
52
|
-
label:
|
|
53
|
-
description:
|
|
54
|
-
icon: /* @__PURE__ */
|
|
52
|
+
label: k("conversations.public", "Public"),
|
|
53
|
+
description: k("conversations.publicDescription", "Anyone in the workspace can discover and join"),
|
|
54
|
+
icon: /* @__PURE__ */ h(l, { size: 16 })
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
value: "PRIVATE",
|
|
58
|
-
label:
|
|
59
|
-
description:
|
|
60
|
-
icon: /* @__PURE__ */
|
|
58
|
+
label: k("conversations.private", "Private"),
|
|
59
|
+
description: k("conversations.privateDescription", "Only invited members can access"),
|
|
60
|
+
icon: /* @__PURE__ */ h(d, { size: 16 })
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
value: "PROTECTED",
|
|
64
|
-
label:
|
|
65
|
-
description:
|
|
66
|
-
icon: /* @__PURE__ */
|
|
64
|
+
label: k("conversations.protected", "Protected"),
|
|
65
|
+
description: k("conversations.protectedDescription", "Discoverable but requires approval to join"),
|
|
66
|
+
icon: /* @__PURE__ */ h(re, { size: 16 })
|
|
67
67
|
}
|
|
68
|
-
], [
|
|
69
|
-
|
|
70
|
-
}, []),
|
|
71
|
-
|
|
72
|
-
}, [
|
|
73
|
-
|
|
74
|
-
}, []), Z =
|
|
75
|
-
e ||
|
|
76
|
-
}, [x,
|
|
77
|
-
if (!(Z &&
|
|
68
|
+
], [k]), [F, I] = c("GROUP"), [L, R] = c(""), [z, B] = c(""), [V, H] = c("PRIVATE"), [U, W] = c([]), G = F === "DIRECT", K = !G, q = G ? he : pe, J = G ? me : fe, Y = s(() => U.map((e) => e.id), [U]), X = o(() => {
|
|
69
|
+
I("GROUP"), R(""), B(""), H("PRIVATE"), W([]);
|
|
70
|
+
}, []), ge = o((e) => {
|
|
71
|
+
W((t) => t.some((t) => t.id === e.id) ? t : G ? [e] : [...t, e]);
|
|
72
|
+
}, [G]), _e = o((e) => {
|
|
73
|
+
W((t) => t.filter((t) => t.id !== e));
|
|
74
|
+
}, []), Z = F === "DIRECT" || F === "GROUP", ve = s(() => D?.id ? [D.id] : [], [D?.id]), Q = o((e) => {
|
|
75
|
+
e || X(), x(e);
|
|
76
|
+
}, [x, X]), ye = o(async () => {
|
|
77
|
+
if (!(Z && Y.length === 0) && !(K && !L.trim())) {
|
|
78
78
|
te("light");
|
|
79
79
|
try {
|
|
80
|
-
let e = await T({
|
|
81
|
-
type:
|
|
82
|
-
title:
|
|
83
|
-
description:
|
|
84
|
-
participantIds:
|
|
85
|
-
visibility:
|
|
80
|
+
let e = G ? await E(Y[0]) : await T({
|
|
81
|
+
type: F,
|
|
82
|
+
title: K ? L.trim() : void 0,
|
|
83
|
+
description: z.trim() || void 0,
|
|
84
|
+
participantIds: Y,
|
|
85
|
+
visibility: V
|
|
86
86
|
});
|
|
87
87
|
if (!e) {
|
|
88
|
-
|
|
88
|
+
a("error");
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
a("success"), O.emit("conversations.conversation.created", {
|
|
92
92
|
conversationId: e.id,
|
|
93
|
-
conversationType:
|
|
93
|
+
conversationType: F,
|
|
94
94
|
source: "new-conversation-dialog"
|
|
95
|
-
}),
|
|
95
|
+
}), X(), x(!1), w?.(e.id);
|
|
96
96
|
} catch (e) {
|
|
97
|
-
if (
|
|
98
|
-
|
|
97
|
+
if (m(e)) {
|
|
98
|
+
X(), M(!0), S ? S() : x(!1);
|
|
99
99
|
return;
|
|
100
100
|
}
|
|
101
|
-
throw
|
|
101
|
+
throw a("error"), e;
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
}, [
|
|
105
|
-
|
|
105
|
+
O,
|
|
106
106
|
Z,
|
|
107
|
-
J,
|
|
108
|
-
q,
|
|
109
|
-
R,
|
|
110
|
-
T,
|
|
111
|
-
I,
|
|
112
|
-
B,
|
|
113
|
-
H,
|
|
114
107
|
Y,
|
|
108
|
+
K,
|
|
109
|
+
L,
|
|
110
|
+
T,
|
|
111
|
+
E,
|
|
112
|
+
F,
|
|
113
|
+
z,
|
|
114
|
+
V,
|
|
115
|
+
X,
|
|
115
116
|
x,
|
|
116
117
|
S,
|
|
117
|
-
w
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
w,
|
|
119
|
+
G
|
|
120
|
+
]), be = o(() => {
|
|
121
|
+
M(!1), C?.();
|
|
122
|
+
}, [C]), $ = (!Z || Y.length > 0) && (!K || L.trim().length > 0);
|
|
123
|
+
return /* @__PURE__ */ g(se, { children: [/* @__PURE__ */ h(ce, {
|
|
122
124
|
open: b,
|
|
123
125
|
onOpenChange: Q,
|
|
124
|
-
children: /* @__PURE__ */
|
|
126
|
+
children: /* @__PURE__ */ g(le, {
|
|
125
127
|
className: "sm:max-w-[640px] max-h-[90vh] overflow-y-auto",
|
|
126
128
|
"data-tour": "new-conversation-dialog",
|
|
127
129
|
children: [
|
|
128
|
-
/* @__PURE__ */
|
|
129
|
-
/* @__PURE__ */
|
|
130
|
+
/* @__PURE__ */ g(de, { children: [/* @__PURE__ */ h(v, { children: k("conversations.newConversation", "New Conversation") }), /* @__PURE__ */ h(ue, { children: k("conversations.newConversationSubtitle", "Start a new conversation with your team") })] }),
|
|
131
|
+
/* @__PURE__ */ g("div", {
|
|
130
132
|
className: "space-y-6",
|
|
131
133
|
children: [
|
|
132
|
-
/* @__PURE__ */
|
|
134
|
+
/* @__PURE__ */ g("section", {
|
|
133
135
|
"data-tour": "conversation-type-selector",
|
|
134
136
|
className: "space-y-3",
|
|
135
|
-
children: [/* @__PURE__ */
|
|
137
|
+
children: [/* @__PURE__ */ h("h3", {
|
|
136
138
|
className: "text-sm font-semibold text-text-primary",
|
|
137
|
-
children:
|
|
138
|
-
}), /* @__PURE__ */
|
|
139
|
+
children: k("conversations.conversationType", "Conversation Type")
|
|
140
|
+
}), /* @__PURE__ */ h("div", {
|
|
139
141
|
className: "grid grid-cols-1 sm:grid-cols-2 gap-3",
|
|
140
|
-
children:
|
|
142
|
+
children: N.map((e) => /* @__PURE__ */ g("button", {
|
|
141
143
|
type: "button",
|
|
142
144
|
onClick: () => {
|
|
143
|
-
|
|
145
|
+
I(e.type), e.type === "DIRECT" && W((e) => e.slice(0, 1));
|
|
144
146
|
},
|
|
145
|
-
className: n("flex items-start gap-3 p-4 rounded-xl border text-left transition-all duration-150",
|
|
146
|
-
children: [/* @__PURE__ */
|
|
147
|
-
className: n("p-2 rounded-lg",
|
|
147
|
+
className: n("flex items-start gap-3 p-4 rounded-xl border text-left transition-all duration-150", F === e.type ? "border-primary bg-action-primary-bg/5 ring-1 ring-primary/20" : "border-border-subtle hover:border-muted-foreground/30 hover:bg-bg-sunken/30"),
|
|
148
|
+
children: [/* @__PURE__ */ h("div", {
|
|
149
|
+
className: n("p-2 rounded-lg", F === e.type ? "bg-action-primary-bg/10 text-primary" : "bg-bg-sunken text-text-secondary"),
|
|
148
150
|
children: e.icon
|
|
149
|
-
}), /* @__PURE__ */
|
|
151
|
+
}), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("p", {
|
|
150
152
|
className: "text-sm font-medium text-text-primary",
|
|
151
153
|
children: e.label
|
|
152
|
-
}), /* @__PURE__ */
|
|
154
|
+
}), /* @__PURE__ */ h("p", {
|
|
153
155
|
className: "text-xs text-text-secondary mt-0.5",
|
|
154
156
|
children: e.description
|
|
155
157
|
})] })]
|
|
156
158
|
}, e.type))
|
|
157
159
|
})]
|
|
158
160
|
}),
|
|
159
|
-
!
|
|
161
|
+
!G && /* @__PURE__ */ g("section", {
|
|
160
162
|
"data-tour": "conversation-visibility-selector",
|
|
161
163
|
className: "space-y-3",
|
|
162
164
|
children: [
|
|
163
|
-
/* @__PURE__ */
|
|
165
|
+
/* @__PURE__ */ h("h3", {
|
|
164
166
|
className: "text-sm font-semibold text-text-primary",
|
|
165
|
-
children:
|
|
167
|
+
children: k("conversations.visibility", "Visibility")
|
|
166
168
|
}),
|
|
167
|
-
/* @__PURE__ */
|
|
169
|
+
/* @__PURE__ */ h("div", {
|
|
168
170
|
className: "flex gap-2 flex-wrap",
|
|
169
|
-
children:
|
|
171
|
+
children: P.map((e) => /* @__PURE__ */ g("button", {
|
|
170
172
|
type: "button",
|
|
171
|
-
onClick: () =>
|
|
172
|
-
className: n("flex items-center gap-2 px-4 py-2.5 rounded-lg border text-sm transition-all",
|
|
173
|
+
onClick: () => H(e.value),
|
|
174
|
+
className: n("flex items-center gap-2 px-4 py-2.5 rounded-lg border text-sm transition-all", V === e.value ? "border-primary bg-action-primary-bg/5 text-primary font-medium" : "border-border-subtle text-text-secondary hover:text-text-primary hover:bg-bg-sunken/30"),
|
|
173
175
|
children: [e.icon, e.label]
|
|
174
176
|
}, e.value))
|
|
175
177
|
}),
|
|
176
|
-
/* @__PURE__ */
|
|
178
|
+
/* @__PURE__ */ h("p", {
|
|
177
179
|
className: "text-xs text-text-secondary",
|
|
178
|
-
children:
|
|
180
|
+
children: P.find((e) => e.value === V)?.description
|
|
179
181
|
})
|
|
180
182
|
]
|
|
181
183
|
}),
|
|
182
|
-
|
|
184
|
+
K && /* @__PURE__ */ g("section", {
|
|
183
185
|
"data-tour": "conversation-create-details",
|
|
184
186
|
className: "space-y-3",
|
|
185
|
-
children: [/* @__PURE__ */
|
|
187
|
+
children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ g("label", {
|
|
186
188
|
htmlFor: "conv-title",
|
|
187
189
|
className: "text-sm font-semibold text-text-primary",
|
|
188
190
|
children: [
|
|
189
|
-
|
|
191
|
+
k("conversations.name", "Name"),
|
|
190
192
|
" ",
|
|
191
|
-
/* @__PURE__ */
|
|
193
|
+
/* @__PURE__ */ h("span", {
|
|
192
194
|
className: "text-status-error-text",
|
|
193
195
|
children: "*"
|
|
194
196
|
})
|
|
195
197
|
]
|
|
196
|
-
}), /* @__PURE__ */
|
|
198
|
+
}), /* @__PURE__ */ h("input", {
|
|
197
199
|
id: "conv-title",
|
|
198
200
|
type: "text",
|
|
199
|
-
value:
|
|
200
|
-
onChange: (e) =>
|
|
201
|
-
placeholder:
|
|
201
|
+
value: L,
|
|
202
|
+
onChange: (e) => R(e.target.value),
|
|
203
|
+
placeholder: F === "CHANNEL" ? k("conversations.channelNamePlaceholder", "e.g., engineering-general") : k("conversations.conversationNamePlaceholder", "e.g., Product Launch Q1"),
|
|
202
204
|
maxLength: 200,
|
|
203
205
|
className: "mt-1.5 w-full px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-surface placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
204
|
-
})] }), /* @__PURE__ */
|
|
206
|
+
})] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
205
207
|
htmlFor: "conv-desc",
|
|
206
208
|
className: "text-sm font-semibold text-text-primary",
|
|
207
|
-
children:
|
|
208
|
-
}), /* @__PURE__ */
|
|
209
|
+
children: k("conversations.description", "Description")
|
|
210
|
+
}), /* @__PURE__ */ h("textarea", {
|
|
209
211
|
id: "conv-desc",
|
|
210
|
-
value:
|
|
211
|
-
onChange: (e) =>
|
|
212
|
-
placeholder:
|
|
212
|
+
value: z,
|
|
213
|
+
onChange: (e) => B(e.target.value),
|
|
214
|
+
placeholder: k("conversations.descriptionPlaceholder", "What is this conversation about?"),
|
|
213
215
|
rows: 2,
|
|
214
216
|
maxLength: 1e3,
|
|
215
217
|
className: "mt-1.5 w-full px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-surface placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none"
|
|
216
218
|
})] })]
|
|
217
219
|
}),
|
|
218
|
-
/* @__PURE__ */
|
|
220
|
+
/* @__PURE__ */ g("section", {
|
|
219
221
|
"data-tour": "conversation-participants",
|
|
220
222
|
className: "space-y-3",
|
|
221
|
-
children: [/* @__PURE__ */
|
|
223
|
+
children: [/* @__PURE__ */ g("h3", {
|
|
222
224
|
className: "text-sm font-semibold text-text-primary",
|
|
223
|
-
children: [
|
|
225
|
+
children: [G ? k("conversations.recipient", "Recipient") : k("conversations.participants", "Participants"), Z ? /* @__PURE__ */ h("span", {
|
|
224
226
|
className: "ml-1 text-status-error-text",
|
|
225
227
|
children: "*"
|
|
226
|
-
}) : /* @__PURE__ */
|
|
228
|
+
}) : /* @__PURE__ */ g("span", {
|
|
227
229
|
className: "ml-1.5 text-xs font-normal text-text-secondary",
|
|
228
230
|
children: [
|
|
229
231
|
"(",
|
|
230
|
-
|
|
232
|
+
k("conversations.optional", "optional"),
|
|
231
233
|
")"
|
|
232
234
|
]
|
|
233
235
|
})]
|
|
234
|
-
}), /* @__PURE__ */
|
|
235
|
-
selected:
|
|
236
|
-
onAdd:
|
|
237
|
-
onRemove:
|
|
238
|
-
single:
|
|
239
|
-
excludeIds:
|
|
236
|
+
}), /* @__PURE__ */ h(ne, {
|
|
237
|
+
selected: U,
|
|
238
|
+
onAdd: ge,
|
|
239
|
+
onRemove: _e,
|
|
240
|
+
single: G,
|
|
241
|
+
excludeIds: ve
|
|
240
242
|
})]
|
|
241
243
|
}),
|
|
242
|
-
|
|
244
|
+
q && !m(q) && /* @__PURE__ */ h("div", {
|
|
243
245
|
className: "text-xs text-status-error-text rounded-lg border border-status-error-border/30 bg-status-error-bg/10 px-3 py-2",
|
|
244
|
-
children:
|
|
246
|
+
children: y(q)
|
|
245
247
|
})
|
|
246
248
|
]
|
|
247
249
|
}),
|
|
248
|
-
/* @__PURE__ */
|
|
250
|
+
/* @__PURE__ */ g(_, { children: [/* @__PURE__ */ h("button", {
|
|
249
251
|
type: "button",
|
|
250
252
|
onClick: () => Q(!1),
|
|
251
253
|
className: "px-4 py-2 text-sm font-medium rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-sunken/50 transition-colors",
|
|
252
|
-
children:
|
|
253
|
-
}), /* @__PURE__ */
|
|
254
|
+
children: k("conversations.cancel", "Cancel")
|
|
255
|
+
}), /* @__PURE__ */ h("button", {
|
|
254
256
|
type: "button",
|
|
255
|
-
onClick: () => void
|
|
256
|
-
disabled: !$ ||
|
|
257
|
-
className: n("inline-flex items-center gap-2 px-5 py-2 text-sm font-medium rounded-lg transition-all", $ && !
|
|
258
|
-
children:
|
|
257
|
+
onClick: () => void ye(),
|
|
258
|
+
disabled: !$ || J,
|
|
259
|
+
className: n("inline-flex items-center gap-2 px-5 py-2 text-sm font-medium rounded-lg transition-all", $ && !J ? "bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 shadow-sm" : "bg-bg-sunken text-text-secondary cursor-not-allowed"),
|
|
260
|
+
children: J ? k("conversations.creating", "Creating...") : k("conversations.createConversation", "Create Conversation")
|
|
259
261
|
})] })
|
|
260
262
|
]
|
|
261
263
|
})
|
|
262
|
-
}), /* @__PURE__ */
|
|
263
|
-
open:
|
|
264
|
-
title:
|
|
265
|
-
description:
|
|
266
|
-
onClose:
|
|
264
|
+
}), /* @__PURE__ */ h(i, {
|
|
265
|
+
open: j,
|
|
266
|
+
title: k("conversations.quota.conversations.title", "Conversation Quota Reached"),
|
|
267
|
+
description: k("conversations.quota.conversations.description", "You've reached the conversation limit for your current plan. View billing to upgrade or add more capacity."),
|
|
268
|
+
onClose: be,
|
|
267
269
|
onConfirm: () => {
|
|
268
|
-
|
|
270
|
+
M(!1), A("/billing");
|
|
269
271
|
}
|
|
270
272
|
})] });
|
|
271
273
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NewConversationDialog.js","names":[],"sources":["../../../src/conversations/components/NewConversationDialog.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from \"react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@burdenoff/fe-libs/ui\";\nimport {\n Globe,\n Hash,\n Lock,\n Megaphone,\n MessageCircle,\n Shield,\n Users,\n} from \"lucide-react\";\nimport { useNavigate } from \"react-router\";\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\nimport {\n useCreateConversation,\n type WorkspaceDirectoryMember,\n} from \"../hooks/useConversationQueries\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { MemberPicker } from \"./MemberPicker\";\nimport { QuotaExhaustedDialog } from \"./QuotaExhaustedDialog\";\nimport type { ConversationType, ConversationVisibility } from \"../types\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n/** Extract the first meaningful GraphQL error message from an ApolloError. */\nfunction getErrorMessage(error: Error): string {\n if (\"graphQLErrors\" in error) {\n const gqlErrors = (error as { graphQLErrors?: Array<{ message?: string }> })\n .graphQLErrors;\n if (gqlErrors && gqlErrors.length > 0 && gqlErrors[0]?.message) {\n return gqlErrors[0].message;\n }\n }\n return error.message;\n}\n\ninterface ConversationTypeOption {\n type: ConversationType;\n label: string;\n description: string;\n icon: React.ReactNode;\n}\n\nexport interface NewConversationDialogProps {\n /** Whether the dialog is open. */\n open: boolean;\n /** Open-state change handler. Fires with `false` on close (overlay click,\n * Escape, X button, Cancel). */\n onOpenChange: (open: boolean) => void;\n /**\n * Optional quota-failure hook for route wrappers that need to close the\n * create modal without running their normal close navigation.\n */\n onQuotaExhausted?: () => void;\n /** Called when the quota dialog is dismissed without opening billing. */\n onQuotaDialogClose?: () => void;\n /**\n * Called after a conversation is successfully created. Receives the new\n * conversation id so the parent can navigate to the detail route.\n * The dialog also closes itself (via `onOpenChange(false)`) before this\n * fires, so the parent does not need to close the dialog manually.\n */\n onCreated?: (conversationId: string) => void;\n}\n\n/**\n * NewConversationDialog — modal form for creating a new conversation.\n *\n * Renders inside the canonical `<Dialog>` primitive from\n * `@burdenoff/fe-libs/ui` so it inherits theme tokens, focus management,\n * and overlay behaviour from the shell.\n *\n * Form state is owned locally; on successful submit the dialog clears\n * state, closes itself, and invokes `onCreated` with the new\n * conversation id.\n */\nexport function NewConversationDialog({\n open,\n onOpenChange,\n onQuotaExhausted,\n onQuotaDialogClose,\n onCreated,\n}: NewConversationDialogProps) {\n const { createConversation, loading, error } = useCreateConversation();\n const { currentUser } = useConversations();\n const bus = useEventBus();\n const tr = useTr();\n const navigate = useNavigate();\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n\n const CONVERSATION_TYPES: ConversationTypeOption[] = useMemo(\n () => [\n {\n type: \"DIRECT\" as ConversationType,\n label: tr(\"conversations.directMessage\", \"Direct Message\"),\n description: tr(\n \"conversations.directMessageDescription\",\n \"One-on-one private conversation\",\n ),\n icon: <MessageCircle size={20} />,\n },\n {\n type: \"GROUP\" as ConversationType,\n label: tr(\"conversations.groupChat\", \"Group Chat\"),\n description: tr(\n \"conversations.groupChatDescription\",\n \"Invite multiple people to a group conversation\",\n ),\n icon: <Users size={20} />,\n },\n {\n type: \"CHANNEL\" as ConversationType,\n label: tr(\"conversations.channel\", \"Channel\"),\n description: tr(\n \"conversations.channelDescription\",\n \"A topic-based channel that anyone can join\",\n ),\n icon: <Hash size={20} />,\n },\n {\n type: \"ANNOUNCEMENT\" as ConversationType,\n label: tr(\"conversations.announcement\", \"Announcement\"),\n description: tr(\n \"conversations.announcementDescription\",\n \"Broadcast messages to a large audience\",\n ),\n icon: <Megaphone size={20} />,\n },\n ],\n [tr],\n );\n\n const VISIBILITY_OPTIONS: Array<{\n value: ConversationVisibility;\n label: string;\n description: string;\n icon: React.ReactNode;\n }> = useMemo(\n () => [\n {\n value: \"PUBLIC\" as ConversationVisibility,\n label: tr(\"conversations.public\", \"Public\"),\n description: tr(\n \"conversations.publicDescription\",\n \"Anyone in the workspace can discover and join\",\n ),\n icon: <Globe size={16} />,\n },\n {\n value: \"PRIVATE\" as ConversationVisibility,\n label: tr(\"conversations.private\", \"Private\"),\n description: tr(\n \"conversations.privateDescription\",\n \"Only invited members can access\",\n ),\n icon: <Lock size={16} />,\n },\n {\n value: \"PROTECTED\" as ConversationVisibility,\n label: tr(\"conversations.protected\", \"Protected\"),\n description: tr(\n \"conversations.protectedDescription\",\n \"Discoverable but requires approval to join\",\n ),\n icon: <Shield size={16} />,\n },\n ],\n [tr],\n );\n\n const [selectedType, setSelectedType] = useState<ConversationType>(\"GROUP\");\n const [title, setTitle] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [visibility, setVisibility] =\n useState<ConversationVisibility>(\"PRIVATE\");\n const [selectedMembers, setSelectedMembers] = useState<\n WorkspaceDirectoryMember[]\n >([]);\n\n const isDirectMessage = selectedType === \"DIRECT\";\n const needsTitle = !isDirectMessage;\n\n const participantIds = useMemo(\n () => selectedMembers.map((member) => member.id),\n [selectedMembers],\n );\n\n const resetForm = useCallback(() => {\n setSelectedType(\"GROUP\");\n setTitle(\"\");\n setDescription(\"\");\n setVisibility(\"PRIVATE\");\n setSelectedMembers([]);\n }, []);\n\n const addMember = useCallback(\n (member: WorkspaceDirectoryMember) => {\n setSelectedMembers((prev) => {\n if (prev.some((existing) => existing.id === member.id)) return prev;\n // A direct message has exactly one recipient.\n return isDirectMessage ? [member] : [...prev, member];\n });\n },\n [isDirectMessage],\n );\n\n const removeMember = useCallback((userId: string) => {\n setSelectedMembers((prev) => prev.filter((member) => member.id !== userId));\n }, []);\n\n const requiresParticipants =\n selectedType === \"DIRECT\" || selectedType === \"GROUP\";\n\n // Never offer the current user as a participant — they are added implicitly\n // as the conversation creator.\n const excludeIds = useMemo(\n () => (currentUser?.id ? [currentUser.id] : []),\n [currentUser?.id],\n );\n\n const handleOpenChange = useCallback(\n (nextOpen: boolean) => {\n if (!nextOpen) {\n resetForm();\n }\n onOpenChange(nextOpen);\n },\n [onOpenChange, resetForm],\n );\n\n const handleCreate = useCallback(async () => {\n if (requiresParticipants && participantIds.length === 0) return;\n if (needsTitle && !title.trim()) return;\n\n nativeImpact(\"light\");\n try {\n const conversation = await createConversation({\n type: selectedType,\n title: needsTitle ? title.trim() : undefined,\n description: description.trim() || undefined,\n participantIds,\n visibility,\n });\n\n if (!conversation) {\n nativeNotify(\"error\");\n return;\n }\n nativeNotify(\"success\");\n bus.emit(\"conversations.conversation.created\", {\n conversationId: conversation.id,\n conversationType: selectedType,\n source: \"new-conversation-dialog\",\n });\n resetForm();\n onOpenChange(false);\n onCreated?.(conversation.id);\n } catch (error) {\n if (isQuotaExhaustedError(error)) {\n resetForm();\n setShowQuotaExhausted(true);\n if (onQuotaExhausted) {\n onQuotaExhausted();\n } else {\n onOpenChange(false);\n }\n return;\n }\n nativeNotify(\"error\");\n throw error;\n }\n }, [\n bus,\n requiresParticipants,\n participantIds,\n needsTitle,\n title,\n createConversation,\n selectedType,\n description,\n visibility,\n resetForm,\n onOpenChange,\n onQuotaExhausted,\n onCreated,\n ]);\n\n const handleQuotaDialogClose = useCallback(() => {\n setShowQuotaExhausted(false);\n onQuotaDialogClose?.();\n }, [onQuotaDialogClose]);\n\n const canCreate =\n (!requiresParticipants || participantIds.length > 0) &&\n (!needsTitle || title.trim().length > 0);\n\n return (\n <>\n <Dialog open={open} onOpenChange={handleOpenChange}>\n <DialogContent\n className=\"sm:max-w-[640px] max-h-[90vh] overflow-y-auto\"\n data-tour=\"new-conversation-dialog\"\n >\n <DialogHeader>\n <DialogTitle>\n {tr(\"conversations.newConversation\", \"New Conversation\")}\n </DialogTitle>\n <DialogDescription>\n {tr(\n \"conversations.newConversationSubtitle\",\n \"Start a new conversation with your team\",\n )}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-6\">\n <section\n data-tour=\"conversation-type-selector\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr(\"conversations.conversationType\", \"Conversation Type\")}\n </h3>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n {CONVERSATION_TYPES.map((option) => (\n <button\n key={option.type}\n type=\"button\"\n onClick={() => {\n setSelectedType(option.type);\n if (option.type === \"DIRECT\") {\n setSelectedMembers((prev) => prev.slice(0, 1));\n }\n }}\n className={cn(\n \"flex items-start gap-3 p-4 rounded-xl border text-left transition-all duration-150\",\n selectedType === option.type\n ? \"border-primary bg-action-primary-bg/5 ring-1 ring-primary/20\"\n : \"border-border-subtle hover:border-muted-foreground/30 hover:bg-bg-sunken/30\",\n )}\n >\n <div\n className={cn(\n \"p-2 rounded-lg\",\n selectedType === option.type\n ? \"bg-action-primary-bg/10 text-primary\"\n : \"bg-bg-sunken text-text-secondary\",\n )}\n >\n {option.icon}\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {option.label}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {option.description}\n </p>\n </div>\n </button>\n ))}\n </div>\n </section>\n\n {!isDirectMessage && (\n <section\n data-tour=\"conversation-visibility-selector\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr(\"conversations.visibility\", \"Visibility\")}\n </h3>\n <div className=\"flex gap-2 flex-wrap\">\n {VISIBILITY_OPTIONS.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => setVisibility(option.value)}\n className={cn(\n \"flex items-center gap-2 px-4 py-2.5 rounded-lg border text-sm transition-all\",\n visibility === option.value\n ? \"border-primary bg-action-primary-bg/5 text-primary font-medium\"\n : \"border-border-subtle text-text-secondary hover:text-text-primary hover:bg-bg-sunken/30\",\n )}\n >\n {option.icon}\n {option.label}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {\n VISIBILITY_OPTIONS.find(\n (option) => option.value === visibility,\n )?.description\n }\n </p>\n </section>\n )}\n\n {needsTitle && (\n <section\n data-tour=\"conversation-create-details\"\n className=\"space-y-3\"\n >\n <div>\n <label\n htmlFor=\"conv-title\"\n className=\"text-sm font-semibold text-text-primary\"\n >\n {tr(\"conversations.name\", \"Name\")}{\" \"}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"conv-title\"\n type=\"text\"\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n placeholder={\n selectedType === \"CHANNEL\"\n ? tr(\n \"conversations.channelNamePlaceholder\",\n \"e.g., engineering-general\",\n )\n : tr(\n \"conversations.conversationNamePlaceholder\",\n \"e.g., Product Launch Q1\",\n )\n }\n maxLength={200}\n className=\"mt-1.5 w-full px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-surface placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label\n htmlFor=\"conv-desc\"\n className=\"text-sm font-semibold text-text-primary\"\n >\n {tr(\"conversations.description\", \"Description\")}\n </label>\n <textarea\n id=\"conv-desc\"\n value={description}\n onChange={(event) => setDescription(event.target.value)}\n placeholder={tr(\n \"conversations.descriptionPlaceholder\",\n \"What is this conversation about?\",\n )}\n rows={2}\n maxLength={1000}\n className=\"mt-1.5 w-full px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-surface placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n </section>\n )}\n\n <section\n data-tour=\"conversation-participants\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {isDirectMessage\n ? tr(\"conversations.recipient\", \"Recipient\")\n : tr(\"conversations.participants\", \"Participants\")}\n {requiresParticipants ? (\n <span className=\"ml-1 text-status-error-text\">*</span>\n ) : (\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n ({tr(\"conversations.optional\", \"optional\")})\n </span>\n )}\n </h3>\n\n <MemberPicker\n selected={selectedMembers}\n onAdd={addMember}\n onRemove={removeMember}\n single={isDirectMessage}\n excludeIds={excludeIds}\n />\n </section>\n\n {error && !isQuotaExhaustedError(error) && (\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 {getErrorMessage(error)}\n </div>\n )}\n </div>\n\n <DialogFooter>\n <button\n type=\"button\"\n onClick={() => handleOpenChange(false)}\n className=\"px-4 py-2 text-sm font-medium rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-sunken/50 transition-colors\"\n >\n {tr(\"conversations.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleCreate()}\n disabled={!canCreate || loading}\n className={cn(\n \"inline-flex items-center gap-2 px-5 py-2 text-sm font-medium rounded-lg transition-all\",\n canCreate && !loading\n ? \"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 shadow-sm\"\n : \"bg-bg-sunken text-text-secondary cursor-not-allowed\",\n )}\n >\n {loading\n ? tr(\"conversations.creating\", \"Creating...\")\n : tr(\"conversations.createConversation\", \"Create Conversation\")}\n </button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <QuotaExhaustedDialog\n open={showQuotaExhausted}\n title={tr(\n \"conversations.quota.conversations.title\",\n \"Conversation Quota Reached\",\n )}\n description={tr(\n \"conversations.quota.conversations.description\",\n \"You've reached the conversation limit for your current plan. View billing to upgrade or add more capacity.\",\n )}\n onClose={handleQuotaDialogClose}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate(\"/billing\");\n }}\n />\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NewConversationDialog.js","names":[],"sources":["../../../src/conversations/components/NewConversationDialog.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from \"react\";\nimport { useEventBus } from \"@burdenoff/fe-libs/shared/events\";\nimport { isQuotaExhaustedError } from \"@burdenoff/fe-libs/shared/utils\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@burdenoff/fe-libs/ui\";\nimport {\n Globe,\n Hash,\n Lock,\n Megaphone,\n MessageCircle,\n Shield,\n Users,\n} from \"lucide-react\";\nimport { useNavigate } from \"react-router\";\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\nimport {\n useCreateConversation,\n useStartDirectConversation,\n type WorkspaceDirectoryMember,\n} from \"../hooks/useConversationQueries\";\nimport { useConversations } from \"../providers/ConversationsProvider\";\nimport { MemberPicker } from \"./MemberPicker\";\nimport { QuotaExhaustedDialog } from \"./QuotaExhaustedDialog\";\nimport type { ConversationType, ConversationVisibility } from \"../types\";\nimport { nativeImpact, nativeNotify } from \"../../utils/nativeBridge\";\n\n/** Extract the first meaningful GraphQL error message from an ApolloError. */\nfunction getErrorMessage(error: Error): string {\n if (\"graphQLErrors\" in error) {\n const gqlErrors = (error as { graphQLErrors?: Array<{ message?: string }> })\n .graphQLErrors;\n if (gqlErrors && gqlErrors.length > 0 && gqlErrors[0]?.message) {\n return gqlErrors[0].message;\n }\n }\n return error.message;\n}\n\ninterface ConversationTypeOption {\n type: ConversationType;\n label: string;\n description: string;\n icon: React.ReactNode;\n}\n\nexport interface NewConversationDialogProps {\n /** Whether the dialog is open. */\n open: boolean;\n /** Open-state change handler. Fires with `false` on close (overlay click,\n * Escape, X button, Cancel). */\n onOpenChange: (open: boolean) => void;\n /**\n * Optional quota-failure hook for route wrappers that need to close the\n * create modal without running their normal close navigation.\n */\n onQuotaExhausted?: () => void;\n /** Called when the quota dialog is dismissed without opening billing. */\n onQuotaDialogClose?: () => void;\n /**\n * Called after a conversation is successfully created. Receives the new\n * conversation id so the parent can navigate to the detail route.\n * The dialog also closes itself (via `onOpenChange(false)`) before this\n * fires, so the parent does not need to close the dialog manually.\n */\n onCreated?: (conversationId: string) => void;\n}\n\n/**\n * NewConversationDialog — modal form for creating a new conversation.\n *\n * Renders inside the canonical `<Dialog>` primitive from\n * `@burdenoff/fe-libs/ui` so it inherits theme tokens, focus management,\n * and overlay behaviour from the shell.\n *\n * Form state is owned locally; on successful submit the dialog clears\n * state, closes itself, and invokes `onCreated` with the new\n * conversation id.\n */\nexport function NewConversationDialog({\n open,\n onOpenChange,\n onQuotaExhausted,\n onQuotaDialogClose,\n onCreated,\n}: NewConversationDialogProps) {\n const {\n createConversation,\n loading,\n error: createError,\n } = useCreateConversation();\n const {\n startDirectConversation,\n loading: startDirectLoading,\n error: startDirectError,\n } = useStartDirectConversation();\n const { currentUser } = useConversations();\n const bus = useEventBus();\n const tr = useTr();\n const navigate = useNavigate();\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n\n const CONVERSATION_TYPES: ConversationTypeOption[] = useMemo(\n () => [\n {\n type: \"DIRECT\" as ConversationType,\n label: tr(\"conversations.directMessage\", \"Direct Message\"),\n description: tr(\n \"conversations.directMessageDescription\",\n \"One-on-one private conversation\",\n ),\n icon: <MessageCircle size={20} />,\n },\n {\n type: \"GROUP\" as ConversationType,\n label: tr(\"conversations.groupChat\", \"Group Chat\"),\n description: tr(\n \"conversations.groupChatDescription\",\n \"Invite multiple people to a group conversation\",\n ),\n icon: <Users size={20} />,\n },\n {\n type: \"CHANNEL\" as ConversationType,\n label: tr(\"conversations.channel\", \"Channel\"),\n description: tr(\n \"conversations.channelDescription\",\n \"A topic-based channel that anyone can join\",\n ),\n icon: <Hash size={20} />,\n },\n {\n type: \"ANNOUNCEMENT\" as ConversationType,\n label: tr(\"conversations.announcement\", \"Announcement\"),\n description: tr(\n \"conversations.announcementDescription\",\n \"Broadcast messages to a large audience\",\n ),\n icon: <Megaphone size={20} />,\n },\n ],\n [tr],\n );\n\n const VISIBILITY_OPTIONS: Array<{\n value: ConversationVisibility;\n label: string;\n description: string;\n icon: React.ReactNode;\n }> = useMemo(\n () => [\n {\n value: \"PUBLIC\" as ConversationVisibility,\n label: tr(\"conversations.public\", \"Public\"),\n description: tr(\n \"conversations.publicDescription\",\n \"Anyone in the workspace can discover and join\",\n ),\n icon: <Globe size={16} />,\n },\n {\n value: \"PRIVATE\" as ConversationVisibility,\n label: tr(\"conversations.private\", \"Private\"),\n description: tr(\n \"conversations.privateDescription\",\n \"Only invited members can access\",\n ),\n icon: <Lock size={16} />,\n },\n {\n value: \"PROTECTED\" as ConversationVisibility,\n label: tr(\"conversations.protected\", \"Protected\"),\n description: tr(\n \"conversations.protectedDescription\",\n \"Discoverable but requires approval to join\",\n ),\n icon: <Shield size={16} />,\n },\n ],\n [tr],\n );\n\n const [selectedType, setSelectedType] = useState<ConversationType>(\"GROUP\");\n const [title, setTitle] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [visibility, setVisibility] =\n useState<ConversationVisibility>(\"PRIVATE\");\n const [selectedMembers, setSelectedMembers] = useState<\n WorkspaceDirectoryMember[]\n >([]);\n\n const isDirectMessage = selectedType === \"DIRECT\";\n const needsTitle = !isDirectMessage;\n\n // The error banner shows whichever hook ran last (createConversation for\n // non-direct types, startDirectConversation for DMs). For DMs\n // createConversation is never called, so its error is always null there.\n const error = isDirectMessage ? startDirectError : createError;\n const isSubmitting = isDirectMessage ? startDirectLoading : loading;\n\n const participantIds = useMemo(\n () => selectedMembers.map((member) => member.id),\n [selectedMembers],\n );\n\n const resetForm = useCallback(() => {\n setSelectedType(\"GROUP\");\n setTitle(\"\");\n setDescription(\"\");\n setVisibility(\"PRIVATE\");\n setSelectedMembers([]);\n }, []);\n\n const addMember = useCallback(\n (member: WorkspaceDirectoryMember) => {\n setSelectedMembers((prev) => {\n if (prev.some((existing) => existing.id === member.id)) return prev;\n // A direct message has exactly one recipient.\n return isDirectMessage ? [member] : [...prev, member];\n });\n },\n [isDirectMessage],\n );\n\n const removeMember = useCallback((userId: string) => {\n setSelectedMembers((prev) => prev.filter((member) => member.id !== userId));\n }, []);\n\n const requiresParticipants =\n selectedType === \"DIRECT\" || selectedType === \"GROUP\";\n\n // Never offer the current user as a participant — they are added implicitly\n // as the conversation creator.\n const excludeIds = useMemo(\n () => (currentUser?.id ? [currentUser.id] : []),\n [currentUser?.id],\n );\n\n const handleOpenChange = useCallback(\n (nextOpen: boolean) => {\n if (!nextOpen) {\n resetForm();\n }\n onOpenChange(nextOpen);\n },\n [onOpenChange, resetForm],\n );\n\n const handleCreate = useCallback(async () => {\n if (requiresParticipants && participantIds.length === 0) return;\n if (needsTitle && !title.trim()) return;\n\n nativeImpact(\"light\");\n try {\n // Direct messages MUST go through getOrCreateDirectConversation so\n // repeated clicks for the same pair reuse the existing thread (the\n // createConversation mutation always mints a new row, which produced\n // duplicate DMs in the inbox — BOFF-8227). For other types the\n // mutation is fine.\n const conversation = isDirectMessage\n ? await startDirectConversation(participantIds[0])\n : await createConversation({\n type: selectedType,\n title: needsTitle ? title.trim() : undefined,\n description: description.trim() || undefined,\n participantIds,\n visibility,\n });\n\n if (!conversation) {\n nativeNotify(\"error\");\n return;\n }\n nativeNotify(\"success\");\n bus.emit(\"conversations.conversation.created\", {\n conversationId: conversation.id,\n conversationType: selectedType,\n source: \"new-conversation-dialog\",\n });\n resetForm();\n onOpenChange(false);\n onCreated?.(conversation.id);\n } catch (error) {\n if (isQuotaExhaustedError(error)) {\n resetForm();\n setShowQuotaExhausted(true);\n if (onQuotaExhausted) {\n onQuotaExhausted();\n } else {\n onOpenChange(false);\n }\n return;\n }\n nativeNotify(\"error\");\n throw error;\n }\n }, [\n bus,\n requiresParticipants,\n participantIds,\n needsTitle,\n title,\n createConversation,\n startDirectConversation,\n selectedType,\n description,\n visibility,\n resetForm,\n onOpenChange,\n onQuotaExhausted,\n onCreated,\n isDirectMessage,\n ]);\n\n const handleQuotaDialogClose = useCallback(() => {\n setShowQuotaExhausted(false);\n onQuotaDialogClose?.();\n }, [onQuotaDialogClose]);\n\n const canCreate =\n (!requiresParticipants || participantIds.length > 0) &&\n (!needsTitle || title.trim().length > 0);\n\n return (\n <>\n <Dialog open={open} onOpenChange={handleOpenChange}>\n <DialogContent\n className=\"sm:max-w-[640px] max-h-[90vh] overflow-y-auto\"\n data-tour=\"new-conversation-dialog\"\n >\n <DialogHeader>\n <DialogTitle>\n {tr(\"conversations.newConversation\", \"New Conversation\")}\n </DialogTitle>\n <DialogDescription>\n {tr(\n \"conversations.newConversationSubtitle\",\n \"Start a new conversation with your team\",\n )}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-6\">\n <section\n data-tour=\"conversation-type-selector\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr(\"conversations.conversationType\", \"Conversation Type\")}\n </h3>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3\">\n {CONVERSATION_TYPES.map((option) => (\n <button\n key={option.type}\n type=\"button\"\n onClick={() => {\n setSelectedType(option.type);\n if (option.type === \"DIRECT\") {\n setSelectedMembers((prev) => prev.slice(0, 1));\n }\n }}\n className={cn(\n \"flex items-start gap-3 p-4 rounded-xl border text-left transition-all duration-150\",\n selectedType === option.type\n ? \"border-primary bg-action-primary-bg/5 ring-1 ring-primary/20\"\n : \"border-border-subtle hover:border-muted-foreground/30 hover:bg-bg-sunken/30\",\n )}\n >\n <div\n className={cn(\n \"p-2 rounded-lg\",\n selectedType === option.type\n ? \"bg-action-primary-bg/10 text-primary\"\n : \"bg-bg-sunken text-text-secondary\",\n )}\n >\n {option.icon}\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {option.label}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {option.description}\n </p>\n </div>\n </button>\n ))}\n </div>\n </section>\n\n {!isDirectMessage && (\n <section\n data-tour=\"conversation-visibility-selector\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr(\"conversations.visibility\", \"Visibility\")}\n </h3>\n <div className=\"flex gap-2 flex-wrap\">\n {VISIBILITY_OPTIONS.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => setVisibility(option.value)}\n className={cn(\n \"flex items-center gap-2 px-4 py-2.5 rounded-lg border text-sm transition-all\",\n visibility === option.value\n ? \"border-primary bg-action-primary-bg/5 text-primary font-medium\"\n : \"border-border-subtle text-text-secondary hover:text-text-primary hover:bg-bg-sunken/30\",\n )}\n >\n {option.icon}\n {option.label}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {\n VISIBILITY_OPTIONS.find(\n (option) => option.value === visibility,\n )?.description\n }\n </p>\n </section>\n )}\n\n {needsTitle && (\n <section\n data-tour=\"conversation-create-details\"\n className=\"space-y-3\"\n >\n <div>\n <label\n htmlFor=\"conv-title\"\n className=\"text-sm font-semibold text-text-primary\"\n >\n {tr(\"conversations.name\", \"Name\")}{\" \"}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"conv-title\"\n type=\"text\"\n value={title}\n onChange={(event) => setTitle(event.target.value)}\n placeholder={\n selectedType === \"CHANNEL\"\n ? tr(\n \"conversations.channelNamePlaceholder\",\n \"e.g., engineering-general\",\n )\n : tr(\n \"conversations.conversationNamePlaceholder\",\n \"e.g., Product Launch Q1\",\n )\n }\n maxLength={200}\n className=\"mt-1.5 w-full px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-surface placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <div>\n <label\n htmlFor=\"conv-desc\"\n className=\"text-sm font-semibold text-text-primary\"\n >\n {tr(\"conversations.description\", \"Description\")}\n </label>\n <textarea\n id=\"conv-desc\"\n value={description}\n onChange={(event) => setDescription(event.target.value)}\n placeholder={tr(\n \"conversations.descriptionPlaceholder\",\n \"What is this conversation about?\",\n )}\n rows={2}\n maxLength={1000}\n className=\"mt-1.5 w-full px-3 py-2 text-sm rounded-lg border border-border-subtle bg-bg-surface placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n </section>\n )}\n\n <section\n data-tour=\"conversation-participants\"\n className=\"space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {isDirectMessage\n ? tr(\"conversations.recipient\", \"Recipient\")\n : tr(\"conversations.participants\", \"Participants\")}\n {requiresParticipants ? (\n <span className=\"ml-1 text-status-error-text\">*</span>\n ) : (\n <span className=\"ml-1.5 text-xs font-normal text-text-secondary\">\n ({tr(\"conversations.optional\", \"optional\")})\n </span>\n )}\n </h3>\n\n <MemberPicker\n selected={selectedMembers}\n onAdd={addMember}\n onRemove={removeMember}\n single={isDirectMessage}\n excludeIds={excludeIds}\n />\n </section>\n\n {error && !isQuotaExhaustedError(error) && (\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 {getErrorMessage(error)}\n </div>\n )}\n </div>\n\n <DialogFooter>\n <button\n type=\"button\"\n onClick={() => handleOpenChange(false)}\n className=\"px-4 py-2 text-sm font-medium rounded-lg text-text-secondary hover:text-text-primary hover:bg-bg-sunken/50 transition-colors\"\n >\n {tr(\"conversations.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleCreate()}\n disabled={!canCreate || isSubmitting}\n className={cn(\n \"inline-flex items-center gap-2 px-5 py-2 text-sm font-medium rounded-lg transition-all\",\n canCreate && !isSubmitting\n ? \"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 shadow-sm\"\n : \"bg-bg-sunken text-text-secondary cursor-not-allowed\",\n )}\n >\n {isSubmitting\n ? tr(\"conversations.creating\", \"Creating...\")\n : tr(\"conversations.createConversation\", \"Create Conversation\")}\n </button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n <QuotaExhaustedDialog\n open={showQuotaExhausted}\n title={tr(\n \"conversations.quota.conversations.title\",\n \"Conversation Quota Reached\",\n )}\n description={tr(\n \"conversations.quota.conversations.description\",\n \"You've reached the conversation limit for your current plan. View billing to upgrade or add more capacity.\",\n )}\n onClose={handleQuotaDialogClose}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate(\"/billing\");\n }}\n />\n </>\n );\n}\n"],"mappings":"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"}
|