@sesionario/blocks 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +634 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -1
- package/dist/index.d.ts +127 -1
- package/dist/index.js +631 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5071,6 +5071,635 @@ function ChatMessageList({
|
|
|
5071
5071
|
/* @__PURE__ */ jsxRuntime.jsx(MessageScrollerButton, {})
|
|
5072
5072
|
] }) });
|
|
5073
5073
|
}
|
|
5074
|
+
function useMediaQuery2(query) {
|
|
5075
|
+
const [matches, setMatches] = React6.useState(false);
|
|
5076
|
+
React6.useEffect(() => {
|
|
5077
|
+
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
5078
|
+
const mql = window.matchMedia(query);
|
|
5079
|
+
const onChange = () => setMatches(mql.matches);
|
|
5080
|
+
onChange();
|
|
5081
|
+
mql.addEventListener("change", onChange);
|
|
5082
|
+
return () => mql.removeEventListener("change", onChange);
|
|
5083
|
+
}, [query]);
|
|
5084
|
+
return matches;
|
|
5085
|
+
}
|
|
5086
|
+
var RECONNECT_DELAY_MS = 2e3;
|
|
5087
|
+
function useSupportConversation({
|
|
5088
|
+
client,
|
|
5089
|
+
getWsUrl,
|
|
5090
|
+
conversationId: conversationIdProp,
|
|
5091
|
+
open = null,
|
|
5092
|
+
enabled = true,
|
|
5093
|
+
onConversationReady,
|
|
5094
|
+
onIncomingMessage
|
|
5095
|
+
}) {
|
|
5096
|
+
const [conversationId, setConversationId] = React6.useState(
|
|
5097
|
+
conversationIdProp
|
|
5098
|
+
);
|
|
5099
|
+
const [messages, setMessages] = React6.useState([]);
|
|
5100
|
+
const [loading, setLoading] = React6.useState(false);
|
|
5101
|
+
const [sending, setSending] = React6.useState(false);
|
|
5102
|
+
const [error, setError] = React6.useState(null);
|
|
5103
|
+
const socketRef = React6.useRef(null);
|
|
5104
|
+
const reconnectRef = React6.useRef(null);
|
|
5105
|
+
const closedByUser = React6.useRef(false);
|
|
5106
|
+
const onReadyRef = React6.useRef(onConversationReady);
|
|
5107
|
+
const onIncomingRef = React6.useRef(onIncomingMessage);
|
|
5108
|
+
onReadyRef.current = onConversationReady;
|
|
5109
|
+
onIncomingRef.current = onIncomingMessage;
|
|
5110
|
+
const load = React6.useCallback(async () => {
|
|
5111
|
+
if (!enabled) return;
|
|
5112
|
+
setLoading(true);
|
|
5113
|
+
setError(null);
|
|
5114
|
+
try {
|
|
5115
|
+
let id = conversationIdProp;
|
|
5116
|
+
if (!id && open) {
|
|
5117
|
+
const conv = await client.findOrCreateConversation(open);
|
|
5118
|
+
id = conv.id;
|
|
5119
|
+
}
|
|
5120
|
+
if (!id) {
|
|
5121
|
+
setConversationId(null);
|
|
5122
|
+
setMessages([]);
|
|
5123
|
+
return;
|
|
5124
|
+
}
|
|
5125
|
+
setConversationId(id);
|
|
5126
|
+
onReadyRef.current?.(id);
|
|
5127
|
+
const msgs = await client.listMessages(id);
|
|
5128
|
+
setMessages(msgs);
|
|
5129
|
+
await client.markRead(id);
|
|
5130
|
+
} catch (err) {
|
|
5131
|
+
setError(err instanceof Error ? err.message : "Chat load failed");
|
|
5132
|
+
} finally {
|
|
5133
|
+
setLoading(false);
|
|
5134
|
+
}
|
|
5135
|
+
}, [client, conversationIdProp, enabled, open]);
|
|
5136
|
+
React6.useEffect(() => {
|
|
5137
|
+
void load();
|
|
5138
|
+
}, [load]);
|
|
5139
|
+
React6.useEffect(() => {
|
|
5140
|
+
closedByUser.current = false;
|
|
5141
|
+
if (!enabled || !conversationId) return;
|
|
5142
|
+
let cancelled = false;
|
|
5143
|
+
const connect = async () => {
|
|
5144
|
+
const url = await getWsUrl(conversationId);
|
|
5145
|
+
if (!url || cancelled || closedByUser.current) return;
|
|
5146
|
+
const socket = new WebSocket(url);
|
|
5147
|
+
socketRef.current = socket;
|
|
5148
|
+
socket.onmessage = (event) => {
|
|
5149
|
+
let data;
|
|
5150
|
+
try {
|
|
5151
|
+
data = JSON.parse(String(event.data));
|
|
5152
|
+
} catch {
|
|
5153
|
+
return;
|
|
5154
|
+
}
|
|
5155
|
+
if (data.type === "message") {
|
|
5156
|
+
setMessages((prev) => {
|
|
5157
|
+
if (prev.some((m) => m.id === data.id)) return prev;
|
|
5158
|
+
return [...prev, data];
|
|
5159
|
+
});
|
|
5160
|
+
onIncomingRef.current?.(data);
|
|
5161
|
+
} else if (data.type === "deleted") {
|
|
5162
|
+
setMessages(
|
|
5163
|
+
(prev) => prev.map(
|
|
5164
|
+
(m) => m.id === data.id ? { ...m, deleted: true, body: "" } : m
|
|
5165
|
+
)
|
|
5166
|
+
);
|
|
5167
|
+
}
|
|
5168
|
+
};
|
|
5169
|
+
socket.onclose = () => {
|
|
5170
|
+
if (cancelled || closedByUser.current) return;
|
|
5171
|
+
reconnectRef.current = setTimeout(() => {
|
|
5172
|
+
void connect();
|
|
5173
|
+
}, RECONNECT_DELAY_MS);
|
|
5174
|
+
};
|
|
5175
|
+
};
|
|
5176
|
+
void connect();
|
|
5177
|
+
return () => {
|
|
5178
|
+
cancelled = true;
|
|
5179
|
+
closedByUser.current = true;
|
|
5180
|
+
if (reconnectRef.current) clearTimeout(reconnectRef.current);
|
|
5181
|
+
socketRef.current?.close();
|
|
5182
|
+
socketRef.current = null;
|
|
5183
|
+
};
|
|
5184
|
+
}, [conversationId, enabled, getWsUrl]);
|
|
5185
|
+
const send = React6.useCallback(
|
|
5186
|
+
async (text) => {
|
|
5187
|
+
const trimmed = text.trim();
|
|
5188
|
+
if (!conversationId || !trimmed || sending) return;
|
|
5189
|
+
setSending(true);
|
|
5190
|
+
setError(null);
|
|
5191
|
+
try {
|
|
5192
|
+
const msg = await client.sendMessage(conversationId, trimmed);
|
|
5193
|
+
setMessages((prev) => {
|
|
5194
|
+
if (prev.some((m) => m.id === msg.id)) return prev;
|
|
5195
|
+
return [...prev, msg];
|
|
5196
|
+
});
|
|
5197
|
+
await client.markRead(conversationId);
|
|
5198
|
+
} catch (err) {
|
|
5199
|
+
setError(err instanceof Error ? err.message : "Send failed");
|
|
5200
|
+
throw err;
|
|
5201
|
+
} finally {
|
|
5202
|
+
setSending(false);
|
|
5203
|
+
}
|
|
5204
|
+
},
|
|
5205
|
+
[client, conversationId, sending]
|
|
5206
|
+
);
|
|
5207
|
+
return {
|
|
5208
|
+
conversationId,
|
|
5209
|
+
messages,
|
|
5210
|
+
loading,
|
|
5211
|
+
sending,
|
|
5212
|
+
error,
|
|
5213
|
+
send,
|
|
5214
|
+
reload: load
|
|
5215
|
+
};
|
|
5216
|
+
}
|
|
5217
|
+
var SupportMessengerContext = React6.createContext(null);
|
|
5218
|
+
function useSupportMessenger() {
|
|
5219
|
+
const ctx = React6.useContext(SupportMessengerContext);
|
|
5220
|
+
if (!ctx) {
|
|
5221
|
+
throw new Error(
|
|
5222
|
+
"useSupportMessenger must be used within SupportMessengerProvider"
|
|
5223
|
+
);
|
|
5224
|
+
}
|
|
5225
|
+
return ctx;
|
|
5226
|
+
}
|
|
5227
|
+
function useSupportMessengerOptional() {
|
|
5228
|
+
return React6.useContext(SupportMessengerContext);
|
|
5229
|
+
}
|
|
5230
|
+
function ThreadPanel({
|
|
5231
|
+
thread,
|
|
5232
|
+
client,
|
|
5233
|
+
getWsUrl,
|
|
5234
|
+
currentUserId,
|
|
5235
|
+
labels,
|
|
5236
|
+
emojiLocale,
|
|
5237
|
+
onClose,
|
|
5238
|
+
onBack,
|
|
5239
|
+
showBack,
|
|
5240
|
+
onConversationReady,
|
|
5241
|
+
className
|
|
5242
|
+
}) {
|
|
5243
|
+
const [draft, setDraft] = React6.useState("");
|
|
5244
|
+
const { messages, loading, sending, error, send } = useSupportConversation({
|
|
5245
|
+
client,
|
|
5246
|
+
getWsUrl,
|
|
5247
|
+
conversationId: thread.conversationId ?? null,
|
|
5248
|
+
open: thread.open ?? null,
|
|
5249
|
+
onConversationReady
|
|
5250
|
+
});
|
|
5251
|
+
async function onSubmit(e) {
|
|
5252
|
+
e.preventDefault();
|
|
5253
|
+
const text = draft.trim();
|
|
5254
|
+
if (!text) return;
|
|
5255
|
+
setDraft("");
|
|
5256
|
+
try {
|
|
5257
|
+
await send(text);
|
|
5258
|
+
} catch {
|
|
5259
|
+
setDraft(text);
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5262
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5263
|
+
"div",
|
|
5264
|
+
{
|
|
5265
|
+
className: cn(
|
|
5266
|
+
"flex h-full min-h-0 flex-col overflow-hidden rounded-xl border bg-card shadow-lg",
|
|
5267
|
+
className
|
|
5268
|
+
),
|
|
5269
|
+
children: [
|
|
5270
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 border-b px-2 py-2", children: [
|
|
5271
|
+
showBack && onBack ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5272
|
+
Button,
|
|
5273
|
+
{
|
|
5274
|
+
type: "button",
|
|
5275
|
+
variant: "ghost",
|
|
5276
|
+
size: "icon-sm",
|
|
5277
|
+
onClick: onBack,
|
|
5278
|
+
"aria-label": labels.back,
|
|
5279
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "size-4" })
|
|
5280
|
+
}
|
|
5281
|
+
) : null,
|
|
5282
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 px-1", children: [
|
|
5283
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-semibold", children: thread.title }),
|
|
5284
|
+
thread.subtitle ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-xs text-muted-foreground", children: thread.subtitle }) : null
|
|
5285
|
+
] }),
|
|
5286
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5287
|
+
Button,
|
|
5288
|
+
{
|
|
5289
|
+
type: "button",
|
|
5290
|
+
variant: "ghost",
|
|
5291
|
+
size: "icon-sm",
|
|
5292
|
+
onClick: onClose,
|
|
5293
|
+
"aria-label": labels.close,
|
|
5294
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4" })
|
|
5295
|
+
}
|
|
5296
|
+
)
|
|
5297
|
+
] }),
|
|
5298
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center p-6", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingState, { label: labels.loading, className: "py-0" }) }) : error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "p-4 text-sm text-destructive", role: "alert", children: error }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
5299
|
+
ChatMessageList,
|
|
5300
|
+
{
|
|
5301
|
+
messages,
|
|
5302
|
+
currentUserId,
|
|
5303
|
+
emptyDescription: labels.emptyThread
|
|
5304
|
+
}
|
|
5305
|
+
) }),
|
|
5306
|
+
/* @__PURE__ */ jsxRuntime.jsxs("form", { className: "flex gap-2 border-t p-3", onSubmit, children: [
|
|
5307
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5308
|
+
EmojiTextarea,
|
|
5309
|
+
{
|
|
5310
|
+
locale: emojiLocale,
|
|
5311
|
+
value: draft,
|
|
5312
|
+
onChange: (e) => setDraft(e.target.value.slice(0, 2e3)),
|
|
5313
|
+
placeholder: labels.placeholder,
|
|
5314
|
+
"aria-label": labels.placeholder,
|
|
5315
|
+
disabled: loading || sending,
|
|
5316
|
+
maxLength: 2e3,
|
|
5317
|
+
rows: 2,
|
|
5318
|
+
className: "min-h-10 flex-1"
|
|
5319
|
+
}
|
|
5320
|
+
),
|
|
5321
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { type: "submit", disabled: !draft.trim() || sending || loading, children: labels.send })
|
|
5322
|
+
] })
|
|
5323
|
+
]
|
|
5324
|
+
}
|
|
5325
|
+
);
|
|
5326
|
+
}
|
|
5327
|
+
function ListPanel({
|
|
5328
|
+
items,
|
|
5329
|
+
labels,
|
|
5330
|
+
tabs,
|
|
5331
|
+
activeTab,
|
|
5332
|
+
onTabChange,
|
|
5333
|
+
onSelect,
|
|
5334
|
+
onClose,
|
|
5335
|
+
searchThreshold,
|
|
5336
|
+
className
|
|
5337
|
+
}) {
|
|
5338
|
+
const [query, setQuery] = React6.useState("");
|
|
5339
|
+
const showSearch = items.length >= searchThreshold;
|
|
5340
|
+
const filtered = React6.useMemo(() => {
|
|
5341
|
+
const q = query.trim().toLowerCase();
|
|
5342
|
+
if (!q) return items;
|
|
5343
|
+
return items.filter(
|
|
5344
|
+
(item) => item.title.toLowerCase().includes(q) || (item.subtitle?.toLowerCase().includes(q) ?? false)
|
|
5345
|
+
);
|
|
5346
|
+
}, [items, query]);
|
|
5347
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5348
|
+
"div",
|
|
5349
|
+
{
|
|
5350
|
+
className: cn(
|
|
5351
|
+
"flex h-full min-h-0 flex-col overflow-hidden rounded-xl border bg-card shadow-lg",
|
|
5352
|
+
className
|
|
5353
|
+
),
|
|
5354
|
+
children: [
|
|
5355
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b px-3 py-2", children: [
|
|
5356
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold", children: labels.listTitle }),
|
|
5357
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5358
|
+
Button,
|
|
5359
|
+
{
|
|
5360
|
+
type: "button",
|
|
5361
|
+
variant: "ghost",
|
|
5362
|
+
size: "icon-sm",
|
|
5363
|
+
onClick: onClose,
|
|
5364
|
+
"aria-label": labels.close,
|
|
5365
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4" })
|
|
5366
|
+
}
|
|
5367
|
+
)
|
|
5368
|
+
] }),
|
|
5369
|
+
tabs && tabs.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 border-b p-2", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
5370
|
+
Button,
|
|
5371
|
+
{
|
|
5372
|
+
type: "button",
|
|
5373
|
+
size: "sm",
|
|
5374
|
+
variant: activeTab === tab.id ? "default" : "outline",
|
|
5375
|
+
onClick: () => onTabChange?.(tab.id),
|
|
5376
|
+
children: tab.label
|
|
5377
|
+
},
|
|
5378
|
+
tab.id
|
|
5379
|
+
)) }) : null,
|
|
5380
|
+
showSearch ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b p-2", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "relative block", children: [
|
|
5381
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-muted-foreground" }),
|
|
5382
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5383
|
+
"input",
|
|
5384
|
+
{
|
|
5385
|
+
value: query,
|
|
5386
|
+
onChange: (e) => setQuery(e.target.value),
|
|
5387
|
+
placeholder: labels.searchPlaceholder ?? "Buscar\u2026",
|
|
5388
|
+
className: "h-8 w-full rounded-md border bg-transparent pr-2 pl-8 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
5389
|
+
}
|
|
5390
|
+
)
|
|
5391
|
+
] }) }) : null,
|
|
5392
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "min-h-0 flex-1 overflow-y-auto p-1", children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("li", { className: "px-3 py-8 text-center text-sm text-muted-foreground", children: labels.emptyList }) : filtered.map((item) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5393
|
+
"button",
|
|
5394
|
+
{
|
|
5395
|
+
type: "button",
|
|
5396
|
+
className: "flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left transition-colors hover:bg-muted/80",
|
|
5397
|
+
onClick: () => onSelect(item),
|
|
5398
|
+
children: [
|
|
5399
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5400
|
+
"span",
|
|
5401
|
+
{
|
|
5402
|
+
className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-full text-xs font-semibold text-white",
|
|
5403
|
+
style: item.avatarUrl ? void 0 : avatarFallbackStyle(item.title),
|
|
5404
|
+
children: item.avatarUrl ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5405
|
+
"img",
|
|
5406
|
+
{
|
|
5407
|
+
src: item.avatarUrl,
|
|
5408
|
+
alt: "",
|
|
5409
|
+
className: "size-full object-cover"
|
|
5410
|
+
}
|
|
5411
|
+
) : getInitials(item.title)
|
|
5412
|
+
}
|
|
5413
|
+
),
|
|
5414
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
5415
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-sm font-medium", children: item.title }),
|
|
5416
|
+
item.subtitle ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-xs text-muted-foreground", children: item.subtitle }) : null
|
|
5417
|
+
] }),
|
|
5418
|
+
(item.unreadCount ?? 0) > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-primary px-2 py-0.5 text-[10px] font-semibold text-primary-foreground", children: (item.unreadCount ?? 0) > 99 ? "99+" : item.unreadCount }) : null
|
|
5419
|
+
]
|
|
5420
|
+
}
|
|
5421
|
+
) }, item.key)) })
|
|
5422
|
+
]
|
|
5423
|
+
}
|
|
5424
|
+
);
|
|
5425
|
+
}
|
|
5426
|
+
function SupportMessengerProvider({
|
|
5427
|
+
children,
|
|
5428
|
+
client,
|
|
5429
|
+
getWsUrl,
|
|
5430
|
+
currentUserId,
|
|
5431
|
+
items,
|
|
5432
|
+
labels,
|
|
5433
|
+
enabled = true,
|
|
5434
|
+
forceMinimized = false,
|
|
5435
|
+
emojiLocale = "es",
|
|
5436
|
+
tabs,
|
|
5437
|
+
activeTab,
|
|
5438
|
+
onTabChange,
|
|
5439
|
+
onRefreshItems,
|
|
5440
|
+
searchThreshold = 8,
|
|
5441
|
+
className
|
|
5442
|
+
}) {
|
|
5443
|
+
const isMobile = useMediaQuery2("(max-width: 767px)");
|
|
5444
|
+
const [listOpen, setListOpen] = React6.useState(false);
|
|
5445
|
+
const [threads, setThreads] = React6.useState([]);
|
|
5446
|
+
const [mobileThreadKey, setMobileThreadKey] = React6.useState(null);
|
|
5447
|
+
const totalUnread = React6.useMemo(
|
|
5448
|
+
() => items.reduce((sum, item) => sum + (item.unreadCount ?? 0), 0),
|
|
5449
|
+
[items]
|
|
5450
|
+
);
|
|
5451
|
+
React6.useEffect(() => {
|
|
5452
|
+
if (!forceMinimized) return;
|
|
5453
|
+
setListOpen(false);
|
|
5454
|
+
setThreads([]);
|
|
5455
|
+
setMobileThreadKey(null);
|
|
5456
|
+
}, [forceMinimized]);
|
|
5457
|
+
const openList = React6.useCallback(() => {
|
|
5458
|
+
setListOpen(true);
|
|
5459
|
+
void onRefreshItems?.();
|
|
5460
|
+
}, [onRefreshItems]);
|
|
5461
|
+
const closeList = React6.useCallback(() => setListOpen(false), []);
|
|
5462
|
+
const toggleList = React6.useCallback(() => {
|
|
5463
|
+
setListOpen((prev) => {
|
|
5464
|
+
const next = !prev;
|
|
5465
|
+
if (next) void onRefreshItems?.();
|
|
5466
|
+
return next;
|
|
5467
|
+
});
|
|
5468
|
+
}, [onRefreshItems]);
|
|
5469
|
+
const openThread = React6.useCallback((req) => {
|
|
5470
|
+
setListOpen(false);
|
|
5471
|
+
setThreads((prev) => {
|
|
5472
|
+
const existing = prev.find((t) => t.key === req.key);
|
|
5473
|
+
if (existing) {
|
|
5474
|
+
return [
|
|
5475
|
+
...prev.filter((t) => t.key !== req.key),
|
|
5476
|
+
{ ...existing, ...req }
|
|
5477
|
+
];
|
|
5478
|
+
}
|
|
5479
|
+
const next = [...prev, req];
|
|
5480
|
+
return next.slice(-2);
|
|
5481
|
+
});
|
|
5482
|
+
setMobileThreadKey(req.key);
|
|
5483
|
+
}, []);
|
|
5484
|
+
const closeThread = React6.useCallback((key) => {
|
|
5485
|
+
setThreads((prev) => prev.filter((t) => t.key !== key));
|
|
5486
|
+
setMobileThreadKey((prev) => prev === key ? null : prev);
|
|
5487
|
+
}, []);
|
|
5488
|
+
const closeAll = React6.useCallback(() => {
|
|
5489
|
+
setListOpen(false);
|
|
5490
|
+
setThreads([]);
|
|
5491
|
+
setMobileThreadKey(null);
|
|
5492
|
+
}, []);
|
|
5493
|
+
const ctx = React6.useMemo(
|
|
5494
|
+
() => ({
|
|
5495
|
+
listOpen,
|
|
5496
|
+
openList,
|
|
5497
|
+
closeList,
|
|
5498
|
+
toggleList,
|
|
5499
|
+
openThread,
|
|
5500
|
+
closeThread,
|
|
5501
|
+
closeAll,
|
|
5502
|
+
activeThreadKey: mobileThreadKey ?? threads[threads.length - 1]?.key ?? null,
|
|
5503
|
+
totalUnread
|
|
5504
|
+
}),
|
|
5505
|
+
[
|
|
5506
|
+
listOpen,
|
|
5507
|
+
openList,
|
|
5508
|
+
closeList,
|
|
5509
|
+
toggleList,
|
|
5510
|
+
openThread,
|
|
5511
|
+
closeThread,
|
|
5512
|
+
closeAll,
|
|
5513
|
+
mobileThreadKey,
|
|
5514
|
+
threads,
|
|
5515
|
+
totalUnread
|
|
5516
|
+
]
|
|
5517
|
+
);
|
|
5518
|
+
const handleSelectItem = React6.useCallback(
|
|
5519
|
+
(item) => {
|
|
5520
|
+
openThread({
|
|
5521
|
+
key: item.key,
|
|
5522
|
+
title: item.title,
|
|
5523
|
+
subtitle: item.subtitle,
|
|
5524
|
+
conversationId: item.conversationId,
|
|
5525
|
+
open: item.open
|
|
5526
|
+
});
|
|
5527
|
+
},
|
|
5528
|
+
[openThread]
|
|
5529
|
+
);
|
|
5530
|
+
const activeMobileThread = threads.find((t) => t.key === mobileThreadKey);
|
|
5531
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(SupportMessengerContext.Provider, { value: ctx, children: [
|
|
5532
|
+
children,
|
|
5533
|
+
enabled ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5534
|
+
"div",
|
|
5535
|
+
{
|
|
5536
|
+
className: cn(
|
|
5537
|
+
"pointer-events-none fixed right-4 bottom-4 z-50 flex flex-col items-end gap-3",
|
|
5538
|
+
className
|
|
5539
|
+
),
|
|
5540
|
+
children: [
|
|
5541
|
+
!isMobile ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-auto flex items-end gap-3", children: [
|
|
5542
|
+
threads.map((thread) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[28rem] w-[22rem]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5543
|
+
ThreadPanel,
|
|
5544
|
+
{
|
|
5545
|
+
thread,
|
|
5546
|
+
client,
|
|
5547
|
+
getWsUrl,
|
|
5548
|
+
currentUserId,
|
|
5549
|
+
labels,
|
|
5550
|
+
emojiLocale,
|
|
5551
|
+
onClose: () => closeThread(thread.key),
|
|
5552
|
+
onConversationReady: () => void onRefreshItems?.()
|
|
5553
|
+
}
|
|
5554
|
+
) }, thread.key)),
|
|
5555
|
+
listOpen ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[28rem] w-[20rem]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5556
|
+
ListPanel,
|
|
5557
|
+
{
|
|
5558
|
+
items,
|
|
5559
|
+
labels,
|
|
5560
|
+
tabs,
|
|
5561
|
+
activeTab,
|
|
5562
|
+
onTabChange,
|
|
5563
|
+
onSelect: handleSelectItem,
|
|
5564
|
+
onClose: closeList,
|
|
5565
|
+
searchThreshold
|
|
5566
|
+
}
|
|
5567
|
+
) }) : null
|
|
5568
|
+
] }) : null,
|
|
5569
|
+
isMobile ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5570
|
+
Drawer,
|
|
5571
|
+
{
|
|
5572
|
+
open: listOpen || Boolean(activeMobileThread),
|
|
5573
|
+
onOpenChange: (open) => {
|
|
5574
|
+
if (!open) closeAll();
|
|
5575
|
+
},
|
|
5576
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(DrawerContent, { className: "pointer-events-auto mx-auto flex h-[85vh] max-w-lg flex-col", children: activeMobileThread ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5577
|
+
/* @__PURE__ */ jsxRuntime.jsx(DrawerHeader, { className: "sr-only", children: /* @__PURE__ */ jsxRuntime.jsx(DrawerTitle, { children: activeMobileThread.title }) }),
|
|
5578
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5579
|
+
ThreadPanel,
|
|
5580
|
+
{
|
|
5581
|
+
thread: activeMobileThread,
|
|
5582
|
+
client,
|
|
5583
|
+
getWsUrl,
|
|
5584
|
+
currentUserId,
|
|
5585
|
+
labels,
|
|
5586
|
+
emojiLocale,
|
|
5587
|
+
showBack: true,
|
|
5588
|
+
onBack: () => {
|
|
5589
|
+
closeThread(activeMobileThread.key);
|
|
5590
|
+
setListOpen(true);
|
|
5591
|
+
},
|
|
5592
|
+
onClose: closeAll,
|
|
5593
|
+
onConversationReady: () => void onRefreshItems?.(),
|
|
5594
|
+
className: "h-full rounded-none border-0 shadow-none"
|
|
5595
|
+
}
|
|
5596
|
+
)
|
|
5597
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5598
|
+
/* @__PURE__ */ jsxRuntime.jsx(DrawerHeader, { className: "sr-only", children: /* @__PURE__ */ jsxRuntime.jsx(DrawerTitle, { children: labels.listTitle }) }),
|
|
5599
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5600
|
+
ListPanel,
|
|
5601
|
+
{
|
|
5602
|
+
items,
|
|
5603
|
+
labels,
|
|
5604
|
+
tabs,
|
|
5605
|
+
activeTab,
|
|
5606
|
+
onTabChange,
|
|
5607
|
+
onSelect: handleSelectItem,
|
|
5608
|
+
onClose: closeAll,
|
|
5609
|
+
searchThreshold,
|
|
5610
|
+
className: "h-full rounded-none border-0 shadow-none"
|
|
5611
|
+
}
|
|
5612
|
+
)
|
|
5613
|
+
] }) })
|
|
5614
|
+
}
|
|
5615
|
+
) : null,
|
|
5616
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5617
|
+
"button",
|
|
5618
|
+
{
|
|
5619
|
+
type: "button",
|
|
5620
|
+
className: cn(
|
|
5621
|
+
"pointer-events-auto relative flex size-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-[transform,opacity] motion-safe:hover:scale-105 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
|
5622
|
+
listOpen && !forceMinimized && "ring-2 ring-ring ring-offset-2"
|
|
5623
|
+
),
|
|
5624
|
+
"aria-label": totalUnread > 0 && labels.unreadBadgeAria ? `${labels.launcherAria}. ${labels.unreadBadgeAria.replace("{count}", String(totalUnread))}` : labels.launcherAria,
|
|
5625
|
+
onClick: () => {
|
|
5626
|
+
if (isMobile) {
|
|
5627
|
+
if (activeMobileThread) {
|
|
5628
|
+
closeAll();
|
|
5629
|
+
} else {
|
|
5630
|
+
toggleList();
|
|
5631
|
+
}
|
|
5632
|
+
return;
|
|
5633
|
+
}
|
|
5634
|
+
toggleList();
|
|
5635
|
+
},
|
|
5636
|
+
children: [
|
|
5637
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageCircle, { className: "size-6" }),
|
|
5638
|
+
totalUnread > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-0.5 -right-0.5 flex size-5 items-center justify-center rounded-full bg-destructive text-[10px] font-bold text-white", children: totalUnread > 99 ? "99+" : totalUnread }) : null
|
|
5639
|
+
]
|
|
5640
|
+
}
|
|
5641
|
+
)
|
|
5642
|
+
]
|
|
5643
|
+
}
|
|
5644
|
+
) : null
|
|
5645
|
+
] });
|
|
5646
|
+
}
|
|
5647
|
+
|
|
5648
|
+
// src/lib/support-chat.ts
|
|
5649
|
+
async function parseJson(res) {
|
|
5650
|
+
const body = await res.json();
|
|
5651
|
+
if (!res.ok) {
|
|
5652
|
+
throw new Error(
|
|
5653
|
+
typeof body === "object" && body && "error" in body && body.error ? String(body.error) : `Chat request failed (${res.status})`
|
|
5654
|
+
);
|
|
5655
|
+
}
|
|
5656
|
+
return body;
|
|
5657
|
+
}
|
|
5658
|
+
function createSupportChatClient(basePath = "/api/chat-svc") {
|
|
5659
|
+
const root = basePath.replace(/\/$/, "");
|
|
5660
|
+
return {
|
|
5661
|
+
basePath: root,
|
|
5662
|
+
async listConversations() {
|
|
5663
|
+
const res = await fetch(`${root}/me/conversations`);
|
|
5664
|
+
const body = await parseJson(res);
|
|
5665
|
+
return body.result ?? [];
|
|
5666
|
+
},
|
|
5667
|
+
async findOrCreateConversation(input) {
|
|
5668
|
+
const res = await fetch(`${root}/conversations`, {
|
|
5669
|
+
method: "POST",
|
|
5670
|
+
headers: { "content-type": "application/json" },
|
|
5671
|
+
body: JSON.stringify(input)
|
|
5672
|
+
});
|
|
5673
|
+
const body = await parseJson(res);
|
|
5674
|
+
return body.result;
|
|
5675
|
+
},
|
|
5676
|
+
async listMessages(conversationId) {
|
|
5677
|
+
const res = await fetch(
|
|
5678
|
+
`${root}/conversations/${encodeURIComponent(conversationId)}/messages`
|
|
5679
|
+
);
|
|
5680
|
+
const body = await parseJson(res);
|
|
5681
|
+
return body.result ?? [];
|
|
5682
|
+
},
|
|
5683
|
+
async sendMessage(conversationId, text) {
|
|
5684
|
+
const res = await fetch(
|
|
5685
|
+
`${root}/conversations/${encodeURIComponent(conversationId)}/messages`,
|
|
5686
|
+
{
|
|
5687
|
+
method: "POST",
|
|
5688
|
+
headers: { "content-type": "application/json" },
|
|
5689
|
+
body: JSON.stringify({ body: text })
|
|
5690
|
+
}
|
|
5691
|
+
);
|
|
5692
|
+
const body = await parseJson(res);
|
|
5693
|
+
return body.result;
|
|
5694
|
+
},
|
|
5695
|
+
async markRead(conversationId) {
|
|
5696
|
+
await fetch(
|
|
5697
|
+
`${root}/conversations/${encodeURIComponent(conversationId)}/read`,
|
|
5698
|
+
{ method: "POST" }
|
|
5699
|
+
);
|
|
5700
|
+
}
|
|
5701
|
+
};
|
|
5702
|
+
}
|
|
5074
5703
|
|
|
5075
5704
|
Object.defineProperty(exports, "useMessageScroller", {
|
|
5076
5705
|
enumerable: true,
|
|
@@ -5128,6 +5757,7 @@ exports.MexicoMap = MexicoMap;
|
|
|
5128
5757
|
exports.OrgSwitcher = OrgSwitcher;
|
|
5129
5758
|
exports.PhoneInput = PhoneInput;
|
|
5130
5759
|
exports.SearchCommand = SearchCommand;
|
|
5760
|
+
exports.SupportMessengerProvider = SupportMessengerProvider;
|
|
5131
5761
|
exports.ThemeSwitcher = ThemeSwitcher;
|
|
5132
5762
|
exports.VerifiedProfessionalBadge = VerifiedProfessionalBadge;
|
|
5133
5763
|
exports.VideoPlayer = VideoPlayer;
|
|
@@ -5135,6 +5765,7 @@ exports.avatarFallbackStyle = avatarFallbackStyle;
|
|
|
5135
5765
|
exports.bubbleVariants = bubbleVariants;
|
|
5136
5766
|
exports.cn = cn;
|
|
5137
5767
|
exports.createLiveHlsConfig = createLiveHlsConfig;
|
|
5768
|
+
exports.createSupportChatClient = createSupportChatClient;
|
|
5138
5769
|
exports.createVodHlsConfig = createVodHlsConfig;
|
|
5139
5770
|
exports.defaultLanguages = defaultLanguages;
|
|
5140
5771
|
exports.describePlaybackError = describePlaybackError;
|
|
@@ -5151,5 +5782,8 @@ exports.useHlsPlayback = useHlsPlayback;
|
|
|
5151
5782
|
exports.useLanguageContext = useLanguageContext;
|
|
5152
5783
|
exports.usePlaybackStats = usePlaybackStats;
|
|
5153
5784
|
exports.useSearchHotkey = useSearchHotkey;
|
|
5785
|
+
exports.useSupportConversation = useSupportConversation;
|
|
5786
|
+
exports.useSupportMessenger = useSupportMessenger;
|
|
5787
|
+
exports.useSupportMessengerOptional = useSupportMessengerOptional;
|
|
5154
5788
|
//# sourceMappingURL=index.cjs.map
|
|
5155
5789
|
//# sourceMappingURL=index.cjs.map
|