@sesionario/blocks 1.42.0-rc.2 → 1.42.0-rc.4
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 +789 -444
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +179 -60
- package/dist/index.d.ts +179 -60
- package/dist/index.js +782 -446
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7675,12 +7675,225 @@ function MessageScrollerButton({
|
|
|
7675
7675
|
}
|
|
7676
7676
|
);
|
|
7677
7677
|
}
|
|
7678
|
+
|
|
7679
|
+
// src/lib/support-chat.ts
|
|
7680
|
+
var CHAT_ATTACHMENT_MIME_TYPES = [
|
|
7681
|
+
"application/pdf",
|
|
7682
|
+
"image/jpeg",
|
|
7683
|
+
"image/png",
|
|
7684
|
+
"image/webp"
|
|
7685
|
+
];
|
|
7686
|
+
var CHAT_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
7687
|
+
var CHAT_ATTACHMENT_MAX_COUNT = 5;
|
|
7688
|
+
var CHAT_ATTACHMENT_ACCEPT = "application/pdf,image/jpeg,image/png,image/webp,.pdf,.jpg,.jpeg,.png,.webp";
|
|
7689
|
+
var EXTENSIONS = {
|
|
7690
|
+
"application/pdf": ["pdf"],
|
|
7691
|
+
"image/jpeg": ["jpg", "jpeg"],
|
|
7692
|
+
"image/png": ["png"],
|
|
7693
|
+
"image/webp": ["webp"]
|
|
7694
|
+
};
|
|
7695
|
+
function isChatAttachmentMime(value) {
|
|
7696
|
+
return CHAT_ATTACHMENT_MIME_TYPES.includes(value);
|
|
7697
|
+
}
|
|
7698
|
+
function isChatImageAttachment(contentType) {
|
|
7699
|
+
return contentType === "image/jpeg" || contentType === "image/png" || contentType === "image/webp";
|
|
7700
|
+
}
|
|
7701
|
+
function filenameMatchesChatMime(filename, mime) {
|
|
7702
|
+
if (!isChatAttachmentMime(mime)) return false;
|
|
7703
|
+
const base = filename.split(/[/\\]/).pop() ?? "";
|
|
7704
|
+
const dot = base.lastIndexOf(".");
|
|
7705
|
+
if (dot <= 0) return false;
|
|
7706
|
+
const ext = base.slice(dot + 1).toLowerCase();
|
|
7707
|
+
return EXTENSIONS[mime].includes(ext);
|
|
7708
|
+
}
|
|
7709
|
+
function validateChatAttachmentFile(file) {
|
|
7710
|
+
if (file.size <= 0 || file.size > CHAT_ATTACHMENT_MAX_BYTES) {
|
|
7711
|
+
return "too_large";
|
|
7712
|
+
}
|
|
7713
|
+
if (!isChatAttachmentMime(file.type) || !filenameMatchesChatMime(file.name, file.type)) {
|
|
7714
|
+
return "bad_type";
|
|
7715
|
+
}
|
|
7716
|
+
return null;
|
|
7717
|
+
}
|
|
7718
|
+
async function parseJson(res) {
|
|
7719
|
+
const body = await res.json();
|
|
7720
|
+
if (!res.ok) {
|
|
7721
|
+
throw new Error(
|
|
7722
|
+
typeof body === "object" && body && "error" in body && body.error ? String(body.error) : `Chat request failed (${res.status})`
|
|
7723
|
+
);
|
|
7724
|
+
}
|
|
7725
|
+
return body;
|
|
7726
|
+
}
|
|
7727
|
+
function createSupportChatClient(basePath = "/api/chat-svc") {
|
|
7728
|
+
const root = basePath.replace(/\/$/, "");
|
|
7729
|
+
const client = {
|
|
7730
|
+
basePath: root,
|
|
7731
|
+
async listConversations() {
|
|
7732
|
+
const res = await fetch(`${root}/me/conversations`);
|
|
7733
|
+
const body = await parseJson(res);
|
|
7734
|
+
return body.result ?? [];
|
|
7735
|
+
},
|
|
7736
|
+
async findOrCreateConversation(input) {
|
|
7737
|
+
const res = await fetch(`${root}/conversations`, {
|
|
7738
|
+
method: "POST",
|
|
7739
|
+
headers: { "content-type": "application/json" },
|
|
7740
|
+
body: JSON.stringify(input)
|
|
7741
|
+
});
|
|
7742
|
+
const body = await parseJson(res);
|
|
7743
|
+
return body.result;
|
|
7744
|
+
},
|
|
7745
|
+
async listMessages(conversationId, opts) {
|
|
7746
|
+
const params = new URLSearchParams();
|
|
7747
|
+
if (opts?.cursor) params.set("cursor", opts.cursor);
|
|
7748
|
+
if (opts?.limit != null) params.set("limit", String(opts.limit));
|
|
7749
|
+
const qs = params.toString();
|
|
7750
|
+
const res = await fetch(
|
|
7751
|
+
`${root}/conversations/${encodeURIComponent(conversationId)}/messages${qs ? `?${qs}` : ""}`
|
|
7752
|
+
);
|
|
7753
|
+
const body = await parseJson(res);
|
|
7754
|
+
return {
|
|
7755
|
+
messages: body.result ?? [],
|
|
7756
|
+
nextCursor: body.nextCursor ?? null
|
|
7757
|
+
};
|
|
7758
|
+
},
|
|
7759
|
+
async sendMessage(conversationId, input) {
|
|
7760
|
+
const body = (input.body ?? "").trim();
|
|
7761
|
+
const attachments = input.attachments ?? [];
|
|
7762
|
+
const payload = {};
|
|
7763
|
+
if (body) payload.body = body;
|
|
7764
|
+
if (attachments.length > 0) payload.attachments = attachments;
|
|
7765
|
+
const res = await fetch(
|
|
7766
|
+
`${root}/conversations/${encodeURIComponent(conversationId)}/messages`,
|
|
7767
|
+
{
|
|
7768
|
+
method: "POST",
|
|
7769
|
+
headers: { "content-type": "application/json" },
|
|
7770
|
+
body: JSON.stringify(payload)
|
|
7771
|
+
}
|
|
7772
|
+
);
|
|
7773
|
+
const parsed = await parseJson(res);
|
|
7774
|
+
return parsed.result;
|
|
7775
|
+
},
|
|
7776
|
+
async createUpload(conversationId, input) {
|
|
7777
|
+
const res = await fetch(
|
|
7778
|
+
`${root}/conversations/${encodeURIComponent(conversationId)}/uploads`,
|
|
7779
|
+
{
|
|
7780
|
+
method: "POST",
|
|
7781
|
+
headers: { "content-type": "application/json" },
|
|
7782
|
+
body: JSON.stringify(input)
|
|
7783
|
+
}
|
|
7784
|
+
);
|
|
7785
|
+
const body = await parseJson(res);
|
|
7786
|
+
return body.result;
|
|
7787
|
+
},
|
|
7788
|
+
async getAttachmentUrls(conversationId, keys) {
|
|
7789
|
+
if (keys.length === 0) return [];
|
|
7790
|
+
const res = await fetch(
|
|
7791
|
+
`${root}/conversations/${encodeURIComponent(conversationId)}/attachment-urls`,
|
|
7792
|
+
{
|
|
7793
|
+
method: "POST",
|
|
7794
|
+
headers: { "content-type": "application/json" },
|
|
7795
|
+
body: JSON.stringify({ keys })
|
|
7796
|
+
}
|
|
7797
|
+
);
|
|
7798
|
+
const body = await parseJson(res);
|
|
7799
|
+
return body.result?.urls ?? [];
|
|
7800
|
+
},
|
|
7801
|
+
async uploadAttachment(conversationId, file) {
|
|
7802
|
+
const validation = validateChatAttachmentFile(file);
|
|
7803
|
+
if (validation === "too_large") {
|
|
7804
|
+
throw new Error("File too large");
|
|
7805
|
+
}
|
|
7806
|
+
if (validation) {
|
|
7807
|
+
throw new Error("File type not allowed");
|
|
7808
|
+
}
|
|
7809
|
+
const upload = await client.createUpload(conversationId, {
|
|
7810
|
+
contentType: file.type,
|
|
7811
|
+
fileName: file.name,
|
|
7812
|
+
sizeBytes: file.size
|
|
7813
|
+
});
|
|
7814
|
+
const put = await fetch(upload.uploadUrl, {
|
|
7815
|
+
method: "PUT",
|
|
7816
|
+
headers: {
|
|
7817
|
+
"content-type": file.type,
|
|
7818
|
+
"content-disposition": upload.contentDisposition
|
|
7819
|
+
},
|
|
7820
|
+
body: file
|
|
7821
|
+
});
|
|
7822
|
+
if (!put.ok) {
|
|
7823
|
+
throw new Error(`upload failed: ${put.status}`);
|
|
7824
|
+
}
|
|
7825
|
+
return {
|
|
7826
|
+
key: upload.key,
|
|
7827
|
+
fileName: file.name,
|
|
7828
|
+
contentType: file.type,
|
|
7829
|
+
sizeBytes: file.size
|
|
7830
|
+
};
|
|
7831
|
+
},
|
|
7832
|
+
async markRead(conversationId) {
|
|
7833
|
+
await fetch(
|
|
7834
|
+
`${root}/conversations/${encodeURIComponent(conversationId)}/read`,
|
|
7835
|
+
{ method: "POST" }
|
|
7836
|
+
);
|
|
7837
|
+
}
|
|
7838
|
+
};
|
|
7839
|
+
return client;
|
|
7840
|
+
}
|
|
7841
|
+
function MessageAttachments({
|
|
7842
|
+
attachments
|
|
7843
|
+
}) {
|
|
7844
|
+
if (attachments.length === 0) return null;
|
|
7845
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1.5 flex flex-col gap-1.5", children: attachments.map((att) => {
|
|
7846
|
+
const url = att.url ?? void 0;
|
|
7847
|
+
if (isChatImageAttachment(att.contentType)) {
|
|
7848
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
7849
|
+
"a",
|
|
7850
|
+
{
|
|
7851
|
+
href: url,
|
|
7852
|
+
target: "_blank",
|
|
7853
|
+
rel: "noopener noreferrer",
|
|
7854
|
+
className: "block overflow-hidden rounded-md",
|
|
7855
|
+
"aria-label": att.fileName,
|
|
7856
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
7857
|
+
SafeImage,
|
|
7858
|
+
{
|
|
7859
|
+
src: url,
|
|
7860
|
+
alt: att.fileName,
|
|
7861
|
+
className: "max-h-48 max-w-full rounded-md object-contain",
|
|
7862
|
+
fallback: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-muted text-muted-foreground flex h-24 w-36 items-center justify-center rounded-md text-xs", children: att.fileName })
|
|
7863
|
+
}
|
|
7864
|
+
)
|
|
7865
|
+
},
|
|
7866
|
+
att.key
|
|
7867
|
+
);
|
|
7868
|
+
}
|
|
7869
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
7870
|
+
"a",
|
|
7871
|
+
{
|
|
7872
|
+
href: url,
|
|
7873
|
+
download: att.fileName,
|
|
7874
|
+
target: "_blank",
|
|
7875
|
+
rel: "noopener noreferrer",
|
|
7876
|
+
className: "bg-background/60 hover:bg-background/80 inline-flex max-w-full items-center gap-2 rounded-md border px-2.5 py-1.5 text-xs font-medium",
|
|
7877
|
+
children: [
|
|
7878
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileTextIcon, { className: "size-3.5 shrink-0" }),
|
|
7879
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: att.fileName })
|
|
7880
|
+
]
|
|
7881
|
+
},
|
|
7882
|
+
att.key
|
|
7883
|
+
);
|
|
7884
|
+
}) });
|
|
7885
|
+
}
|
|
7678
7886
|
function ChatMessageList({
|
|
7679
7887
|
messages,
|
|
7680
7888
|
currentUserId,
|
|
7681
7889
|
emptyTitle = "Sin mensajes",
|
|
7682
7890
|
emptyDescription = "Escribe para iniciar la conversaci\xF3n.",
|
|
7683
|
-
compactEmpty = false
|
|
7891
|
+
compactEmpty = false,
|
|
7892
|
+
hasOlder = false,
|
|
7893
|
+
loadingOlder = false,
|
|
7894
|
+
onLoadOlder,
|
|
7895
|
+
loadOlderLabel = "Cargar mensajes anteriores",
|
|
7896
|
+
loadingOlderLabel = "Cargando\u2026"
|
|
7684
7897
|
}) {
|
|
7685
7898
|
if (messages.length === 0) {
|
|
7686
7899
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -7701,35 +7914,52 @@ function ChatMessageList({
|
|
|
7701
7914
|
);
|
|
7702
7915
|
}
|
|
7703
7916
|
return /* @__PURE__ */ jsxRuntime.jsx(MessageScrollerProvider, { autoScroll: true, children: /* @__PURE__ */ jsxRuntime.jsxs(MessageScroller, { children: [
|
|
7704
|
-
/* @__PURE__ */ jsxRuntime.jsx(MessageScrollerViewport, { children: /* @__PURE__ */ jsxRuntime.
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7917
|
+
/* @__PURE__ */ jsxRuntime.jsx(MessageScrollerViewport, { children: /* @__PURE__ */ jsxRuntime.jsxs(MessageScrollerContent, { className: "gap-4 p-4", children: [
|
|
7918
|
+
hasOlder && onLoadOlder ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center pb-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
7919
|
+
Button,
|
|
7920
|
+
{
|
|
7921
|
+
type: "button",
|
|
7922
|
+
variant: "ghost",
|
|
7923
|
+
size: "sm",
|
|
7924
|
+
disabled: loadingOlder,
|
|
7925
|
+
onClick: onLoadOlder,
|
|
7926
|
+
children: loadingOlder ? loadingOlderLabel : loadOlderLabel
|
|
7927
|
+
}
|
|
7928
|
+
) }) : null,
|
|
7929
|
+
messages.map((m) => {
|
|
7930
|
+
const mine = Boolean(currentUserId) && m.senderId === currentUserId;
|
|
7931
|
+
const align = mine ? "end" : "start";
|
|
7932
|
+
const attachments = m.deleted || !m.attachments ? [] : m.attachments;
|
|
7933
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MessageScrollerItem, { scrollAnchor: mine, children: m.deleted ? /* @__PURE__ */ jsxRuntime.jsx(Marker, { variant: "separator", children: /* @__PURE__ */ jsxRuntime.jsxs(MarkerContent, { children: [
|
|
7934
|
+
"Mensaje eliminado \xB7 ",
|
|
7935
|
+
m.senderName
|
|
7936
|
+
] }) }) : /* @__PURE__ */ jsxRuntime.jsxs(Message, { align, children: [
|
|
7937
|
+
!mine ? /* @__PURE__ */ jsxRuntime.jsx(MessageAvatar, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
7938
|
+
"span",
|
|
7723
7939
|
{
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
children:
|
|
7940
|
+
className: "flex size-8 items-center justify-center rounded-full text-xs font-medium",
|
|
7941
|
+
style: avatarFallbackStyle(m.senderName),
|
|
7942
|
+
children: getInitials(m.senderName)
|
|
7727
7943
|
}
|
|
7728
|
-
),
|
|
7729
|
-
/* @__PURE__ */ jsxRuntime.
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7944
|
+
) }) : null,
|
|
7945
|
+
/* @__PURE__ */ jsxRuntime.jsxs(MessageContent, { children: [
|
|
7946
|
+
!mine ? /* @__PURE__ */ jsxRuntime.jsx(MessageHeader, { children: m.senderName }) : null,
|
|
7947
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
7948
|
+
Bubble,
|
|
7949
|
+
{
|
|
7950
|
+
variant: mine ? "default" : "muted",
|
|
7951
|
+
align,
|
|
7952
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(BubbleContent, { children: [
|
|
7953
|
+
m.body ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap", children: m.body }) : null,
|
|
7954
|
+
/* @__PURE__ */ jsxRuntime.jsx(MessageAttachments, { attachments })
|
|
7955
|
+
] })
|
|
7956
|
+
}
|
|
7957
|
+
),
|
|
7958
|
+
/* @__PURE__ */ jsxRuntime.jsx(MessageFooter, { children: new Date(m.createdAt).toLocaleString("es-MX") })
|
|
7959
|
+
] })
|
|
7960
|
+
] }) }, m.id);
|
|
7961
|
+
})
|
|
7962
|
+
] }) }),
|
|
7733
7963
|
/* @__PURE__ */ jsxRuntime.jsx(MessageScrollerButton, {})
|
|
7734
7964
|
] }) });
|
|
7735
7965
|
}
|
|
@@ -7864,13 +8094,89 @@ var MessageComposer = React2__namespace.forwardRef(function MessageComposer2({
|
|
|
7864
8094
|
);
|
|
7865
8095
|
});
|
|
7866
8096
|
MessageComposer.displayName = "MessageComposer";
|
|
7867
|
-
var
|
|
8097
|
+
var SupportMessengerContext = React2.createContext(null);
|
|
8098
|
+
function useSupportMessenger() {
|
|
8099
|
+
const ctx = React2.useContext(SupportMessengerContext);
|
|
8100
|
+
if (!ctx) {
|
|
8101
|
+
throw new Error(
|
|
8102
|
+
"useSupportMessenger must be used within SupportMessengerProvider"
|
|
8103
|
+
);
|
|
8104
|
+
}
|
|
8105
|
+
return ctx;
|
|
8106
|
+
}
|
|
8107
|
+
function useSupportMessengerOptional() {
|
|
8108
|
+
return React2.useContext(SupportMessengerContext);
|
|
8109
|
+
}
|
|
8110
|
+
function SupportMessengerProvider({
|
|
8111
|
+
children,
|
|
8112
|
+
items,
|
|
8113
|
+
onOpenList,
|
|
8114
|
+
onOpenThread
|
|
8115
|
+
}) {
|
|
8116
|
+
const totalUnread = React2.useMemo(
|
|
8117
|
+
() => items.reduce((sum, item) => sum + (item.unreadCount ?? 0), 0),
|
|
8118
|
+
[items]
|
|
8119
|
+
);
|
|
8120
|
+
const openList = React2.useCallback(() => {
|
|
8121
|
+
onOpenList?.();
|
|
8122
|
+
}, [onOpenList]);
|
|
8123
|
+
const openThread = React2.useCallback(
|
|
8124
|
+
(req) => {
|
|
8125
|
+
onOpenThread?.(req);
|
|
8126
|
+
},
|
|
8127
|
+
[onOpenThread]
|
|
8128
|
+
);
|
|
8129
|
+
const noop = React2.useCallback(() => {
|
|
8130
|
+
}, []);
|
|
8131
|
+
const ctx = React2.useMemo(
|
|
8132
|
+
() => ({
|
|
8133
|
+
listOpen: false,
|
|
8134
|
+
openList,
|
|
8135
|
+
closeList: noop,
|
|
8136
|
+
toggleList: openList,
|
|
8137
|
+
openThread,
|
|
8138
|
+
closeThread: noop,
|
|
8139
|
+
closeAll: noop,
|
|
8140
|
+
activeThreadKey: null,
|
|
8141
|
+
totalUnread
|
|
8142
|
+
}),
|
|
8143
|
+
[openList, openThread, noop, totalUnread]
|
|
8144
|
+
);
|
|
8145
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SupportMessengerContext.Provider, { value: ctx, children });
|
|
8146
|
+
}
|
|
8147
|
+
var RECONNECT_BASE_MS = 1e3;
|
|
8148
|
+
var RECONNECT_MAX_MS = 3e4;
|
|
8149
|
+
function optimisticId() {
|
|
8150
|
+
return `optimistic:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
|
|
8151
|
+
}
|
|
8152
|
+
function attachmentKeySignature(attachments) {
|
|
8153
|
+
return (attachments ?? []).map((a) => a.key).filter((k) => !k.startsWith("local:")).join("\0");
|
|
8154
|
+
}
|
|
8155
|
+
function mergeAttachmentUrls(messages, urlByKey) {
|
|
8156
|
+
let changed = false;
|
|
8157
|
+
const next = messages.map((m) => {
|
|
8158
|
+
if (m.deleted || !m.attachments?.length) return m;
|
|
8159
|
+
let attChanged = false;
|
|
8160
|
+
const attachments = m.attachments.map((a) => {
|
|
8161
|
+
if (a.url || a.key.startsWith("local:")) return a;
|
|
8162
|
+
const url = urlByKey.get(a.key);
|
|
8163
|
+
if (!url) return a;
|
|
8164
|
+
attChanged = true;
|
|
8165
|
+
return { ...a, url };
|
|
8166
|
+
});
|
|
8167
|
+
if (!attChanged) return m;
|
|
8168
|
+
changed = true;
|
|
8169
|
+
return { ...m, attachments };
|
|
8170
|
+
});
|
|
8171
|
+
return changed ? next : messages;
|
|
8172
|
+
}
|
|
7868
8173
|
function useSupportConversation({
|
|
7869
8174
|
client,
|
|
7870
8175
|
getWsUrl,
|
|
7871
8176
|
conversationId: conversationIdProp,
|
|
7872
8177
|
open = null,
|
|
7873
8178
|
enabled = true,
|
|
8179
|
+
currentUserId = null,
|
|
7874
8180
|
onConversationReady,
|
|
7875
8181
|
onIncomingMessage
|
|
7876
8182
|
}) {
|
|
@@ -7878,20 +8184,29 @@ function useSupportConversation({
|
|
|
7878
8184
|
conversationIdProp
|
|
7879
8185
|
);
|
|
7880
8186
|
const [messages, setMessages] = React2.useState([]);
|
|
8187
|
+
const [nextCursor, setNextCursor] = React2.useState(null);
|
|
7881
8188
|
const [loading, setLoading] = React2.useState(false);
|
|
8189
|
+
const [loadingOlder, setLoadingOlder] = React2.useState(false);
|
|
7882
8190
|
const [sending, setSending] = React2.useState(false);
|
|
7883
8191
|
const [error, setError] = React2.useState(null);
|
|
8192
|
+
const [sendError, setSendError] = React2.useState(null);
|
|
7884
8193
|
const socketRef = React2.useRef(null);
|
|
7885
8194
|
const reconnectRef = React2.useRef(null);
|
|
8195
|
+
const reconnectAttempt = React2.useRef(0);
|
|
7886
8196
|
const closedByUser = React2.useRef(false);
|
|
7887
8197
|
const onReadyRef = React2.useRef(onConversationReady);
|
|
7888
8198
|
const onIncomingRef = React2.useRef(onIncomingMessage);
|
|
8199
|
+
const focusedRef = React2.useRef(enabled);
|
|
8200
|
+
const resolvedKeysRef = React2.useRef(/* @__PURE__ */ new Set());
|
|
7889
8201
|
onReadyRef.current = onConversationReady;
|
|
7890
8202
|
onIncomingRef.current = onIncomingMessage;
|
|
8203
|
+
focusedRef.current = enabled;
|
|
7891
8204
|
const load = React2.useCallback(async () => {
|
|
7892
8205
|
if (!enabled) return;
|
|
7893
8206
|
setLoading(true);
|
|
7894
8207
|
setError(null);
|
|
8208
|
+
setSendError(null);
|
|
8209
|
+
resolvedKeysRef.current = /* @__PURE__ */ new Set();
|
|
7895
8210
|
try {
|
|
7896
8211
|
let id = conversationIdProp;
|
|
7897
8212
|
if (!id && open) {
|
|
@@ -7901,12 +8216,14 @@ function useSupportConversation({
|
|
|
7901
8216
|
if (!id) {
|
|
7902
8217
|
setConversationId(null);
|
|
7903
8218
|
setMessages([]);
|
|
8219
|
+
setNextCursor(null);
|
|
7904
8220
|
return;
|
|
7905
8221
|
}
|
|
7906
8222
|
setConversationId(id);
|
|
7907
8223
|
onReadyRef.current?.(id);
|
|
7908
|
-
const
|
|
7909
|
-
setMessages(
|
|
8224
|
+
const page = await client.listMessages(id);
|
|
8225
|
+
setMessages(page.messages);
|
|
8226
|
+
setNextCursor(page.nextCursor);
|
|
7910
8227
|
await client.markRead(id);
|
|
7911
8228
|
} catch (err) {
|
|
7912
8229
|
setError(err instanceof Error ? err.message : "Chat load failed");
|
|
@@ -7917,8 +8234,44 @@ function useSupportConversation({
|
|
|
7917
8234
|
React2.useEffect(() => {
|
|
7918
8235
|
void load();
|
|
7919
8236
|
}, [load]);
|
|
8237
|
+
React2.useEffect(() => {
|
|
8238
|
+
if (!enabled || !conversationId) return;
|
|
8239
|
+
const missing = [];
|
|
8240
|
+
for (const m of messages) {
|
|
8241
|
+
if (m.deleted) continue;
|
|
8242
|
+
for (const a of m.attachments ?? []) {
|
|
8243
|
+
if (a.url || a.key.startsWith("local:")) continue;
|
|
8244
|
+
if (resolvedKeysRef.current.has(a.key)) continue;
|
|
8245
|
+
missing.push(a.key);
|
|
8246
|
+
}
|
|
8247
|
+
}
|
|
8248
|
+
if (missing.length === 0) return;
|
|
8249
|
+
let cancelled = false;
|
|
8250
|
+
const unique = [...new Set(missing)];
|
|
8251
|
+
void client.getAttachmentUrls(conversationId, unique).then((urls) => {
|
|
8252
|
+
if (cancelled) return;
|
|
8253
|
+
const map = /* @__PURE__ */ new Map();
|
|
8254
|
+
for (const row of urls) {
|
|
8255
|
+
map.set(row.key, row.url);
|
|
8256
|
+
resolvedKeysRef.current.add(row.key);
|
|
8257
|
+
}
|
|
8258
|
+
for (const key of unique) {
|
|
8259
|
+
resolvedKeysRef.current.add(key);
|
|
8260
|
+
}
|
|
8261
|
+
setMessages((prev) => mergeAttachmentUrls(prev, map));
|
|
8262
|
+
}).catch(() => {
|
|
8263
|
+
if (cancelled) return;
|
|
8264
|
+
for (const key of unique) {
|
|
8265
|
+
resolvedKeysRef.current.add(key);
|
|
8266
|
+
}
|
|
8267
|
+
});
|
|
8268
|
+
return () => {
|
|
8269
|
+
cancelled = true;
|
|
8270
|
+
};
|
|
8271
|
+
}, [client, conversationId, enabled, messages]);
|
|
7920
8272
|
React2.useEffect(() => {
|
|
7921
8273
|
closedByUser.current = false;
|
|
8274
|
+
reconnectAttempt.current = 0;
|
|
7922
8275
|
if (!enabled || !conversationId) return;
|
|
7923
8276
|
let cancelled = false;
|
|
7924
8277
|
const connect = async () => {
|
|
@@ -7926,6 +8279,9 @@ function useSupportConversation({
|
|
|
7926
8279
|
if (!url || cancelled || closedByUser.current) return;
|
|
7927
8280
|
const socket = new WebSocket(url);
|
|
7928
8281
|
socketRef.current = socket;
|
|
8282
|
+
socket.onopen = () => {
|
|
8283
|
+
reconnectAttempt.current = 0;
|
|
8284
|
+
};
|
|
7929
8285
|
socket.onmessage = (event) => {
|
|
7930
8286
|
let data;
|
|
7931
8287
|
try {
|
|
@@ -7936,22 +8292,46 @@ function useSupportConversation({
|
|
|
7936
8292
|
if (data.type === "message") {
|
|
7937
8293
|
setMessages((prev) => {
|
|
7938
8294
|
if (prev.some((m) => m.id === data.id)) return prev;
|
|
7939
|
-
|
|
8295
|
+
const dataKeys = attachmentKeySignature(data.attachments);
|
|
8296
|
+
const withoutOptimistic = prev.filter((m) => {
|
|
8297
|
+
if (!m.id.startsWith("optimistic:")) return true;
|
|
8298
|
+
if (m.senderId !== data.senderId || m.body !== data.body) {
|
|
8299
|
+
return true;
|
|
8300
|
+
}
|
|
8301
|
+
const atts = m.attachments ?? [];
|
|
8302
|
+
const dataAtts = data.attachments ?? [];
|
|
8303
|
+
const onlyLocal = atts.every((a) => a.key.startsWith("local:"));
|
|
8304
|
+
const realKeys = attachmentKeySignature(atts);
|
|
8305
|
+
if (realKeys && realKeys === dataKeys) return false;
|
|
8306
|
+
if (atts.length === 0 && dataAtts.length === 0) return false;
|
|
8307
|
+
if (onlyLocal && atts.length === dataAtts.length) return false;
|
|
8308
|
+
return true;
|
|
8309
|
+
});
|
|
8310
|
+
return [...withoutOptimistic, data];
|
|
7940
8311
|
});
|
|
7941
8312
|
onIncomingRef.current?.(data);
|
|
8313
|
+
if (focusedRef.current) {
|
|
8314
|
+
void client.markRead(conversationId);
|
|
8315
|
+
}
|
|
7942
8316
|
} else if (data.type === "deleted") {
|
|
7943
8317
|
setMessages(
|
|
7944
8318
|
(prev) => prev.map(
|
|
7945
|
-
(m) => m.id === data.id ? { ...m, deleted: true, body: "" } : m
|
|
8319
|
+
(m) => m.id === data.id ? { ...m, deleted: true, body: "", attachments: [] } : m
|
|
7946
8320
|
)
|
|
7947
8321
|
);
|
|
7948
8322
|
}
|
|
7949
8323
|
};
|
|
7950
8324
|
socket.onclose = () => {
|
|
7951
8325
|
if (cancelled || closedByUser.current) return;
|
|
8326
|
+
const attempt = reconnectAttempt.current;
|
|
8327
|
+
reconnectAttempt.current = attempt + 1;
|
|
8328
|
+
const delay = Math.min(
|
|
8329
|
+
RECONNECT_BASE_MS * 2 ** attempt,
|
|
8330
|
+
RECONNECT_MAX_MS
|
|
8331
|
+
);
|
|
7952
8332
|
reconnectRef.current = setTimeout(() => {
|
|
7953
8333
|
void connect();
|
|
7954
|
-
},
|
|
8334
|
+
}, delay);
|
|
7955
8335
|
};
|
|
7956
8336
|
};
|
|
7957
8337
|
void connect();
|
|
@@ -7962,91 +8342,264 @@ function useSupportConversation({
|
|
|
7962
8342
|
socketRef.current?.close();
|
|
7963
8343
|
socketRef.current = null;
|
|
7964
8344
|
};
|
|
7965
|
-
}, [conversationId, enabled, getWsUrl]);
|
|
8345
|
+
}, [client, conversationId, enabled, getWsUrl]);
|
|
8346
|
+
const loadOlder = React2.useCallback(async () => {
|
|
8347
|
+
if (!conversationId || !nextCursor || loadingOlder) return;
|
|
8348
|
+
setLoadingOlder(true);
|
|
8349
|
+
try {
|
|
8350
|
+
const page = await client.listMessages(conversationId, {
|
|
8351
|
+
cursor: nextCursor
|
|
8352
|
+
});
|
|
8353
|
+
setMessages((prev) => {
|
|
8354
|
+
const seen = new Set(prev.map((m) => m.id));
|
|
8355
|
+
const older = page.messages.filter((m) => !seen.has(m.id));
|
|
8356
|
+
return [...older, ...prev];
|
|
8357
|
+
});
|
|
8358
|
+
setNextCursor(page.nextCursor);
|
|
8359
|
+
} catch {
|
|
8360
|
+
} finally {
|
|
8361
|
+
setLoadingOlder(false);
|
|
8362
|
+
}
|
|
8363
|
+
}, [client, conversationId, loadingOlder, nextCursor]);
|
|
7966
8364
|
const send = React2.useCallback(
|
|
7967
|
-
async (text) => {
|
|
8365
|
+
async (text, files = []) => {
|
|
7968
8366
|
const trimmed = text.trim();
|
|
7969
|
-
if (!conversationId ||
|
|
8367
|
+
if (!conversationId || sending) return;
|
|
8368
|
+
if (!trimmed && files.length === 0) return;
|
|
8369
|
+
if (files.length > CHAT_ATTACHMENT_MAX_COUNT) {
|
|
8370
|
+
setSendError("Too many attachments");
|
|
8371
|
+
throw new Error("Too many attachments");
|
|
8372
|
+
}
|
|
8373
|
+
for (const file of files) {
|
|
8374
|
+
const issue = validateChatAttachmentFile(file);
|
|
8375
|
+
if (issue === "too_large") {
|
|
8376
|
+
setSendError("File too large");
|
|
8377
|
+
throw new Error("File too large");
|
|
8378
|
+
}
|
|
8379
|
+
if (issue) {
|
|
8380
|
+
setSendError("File type not allowed");
|
|
8381
|
+
throw new Error("File type not allowed");
|
|
8382
|
+
}
|
|
8383
|
+
}
|
|
7970
8384
|
setSending(true);
|
|
7971
|
-
|
|
8385
|
+
setSendError(null);
|
|
8386
|
+
const tempId = optimisticId();
|
|
8387
|
+
const localAttachments = files.map(
|
|
8388
|
+
(file, index) => ({
|
|
8389
|
+
key: `local:${tempId}:${index}`,
|
|
8390
|
+
fileName: file.name,
|
|
8391
|
+
contentType: file.type,
|
|
8392
|
+
sizeBytes: file.size,
|
|
8393
|
+
url: URL.createObjectURL(file)
|
|
8394
|
+
})
|
|
8395
|
+
);
|
|
8396
|
+
const optimistic = {
|
|
8397
|
+
id: tempId,
|
|
8398
|
+
conversationId,
|
|
8399
|
+
senderId: currentUserId ?? "me",
|
|
8400
|
+
senderName: "",
|
|
8401
|
+
senderSide: "member",
|
|
8402
|
+
body: trimmed,
|
|
8403
|
+
attachments: localAttachments,
|
|
8404
|
+
deleted: false,
|
|
8405
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8406
|
+
};
|
|
8407
|
+
setMessages((prev) => [...prev, optimistic]);
|
|
7972
8408
|
try {
|
|
7973
|
-
const
|
|
8409
|
+
const uploaded = [];
|
|
8410
|
+
for (const file of files) {
|
|
8411
|
+
uploaded.push(await client.uploadAttachment(conversationId, file));
|
|
8412
|
+
}
|
|
8413
|
+
const msg = await client.sendMessage(conversationId, {
|
|
8414
|
+
body: trimmed || void 0,
|
|
8415
|
+
attachments: uploaded.length > 0 ? uploaded : void 0
|
|
8416
|
+
});
|
|
8417
|
+
for (const att of localAttachments) {
|
|
8418
|
+
if (att.url?.startsWith("blob:")) URL.revokeObjectURL(att.url);
|
|
8419
|
+
}
|
|
7974
8420
|
setMessages((prev) => {
|
|
7975
|
-
|
|
7976
|
-
|
|
8421
|
+
const withoutTemp = prev.filter((m) => m.id !== tempId);
|
|
8422
|
+
if (withoutTemp.some((m) => m.id === msg.id)) return withoutTemp;
|
|
8423
|
+
return [...withoutTemp, msg];
|
|
7977
8424
|
});
|
|
7978
8425
|
await client.markRead(conversationId);
|
|
7979
8426
|
} catch (err) {
|
|
7980
|
-
|
|
8427
|
+
for (const att of localAttachments) {
|
|
8428
|
+
if (att.url?.startsWith("blob:")) URL.revokeObjectURL(att.url);
|
|
8429
|
+
}
|
|
8430
|
+
setMessages((prev) => prev.filter((m) => m.id !== tempId));
|
|
8431
|
+
const message = err instanceof Error ? err.message : "Send failed";
|
|
8432
|
+
setSendError(message);
|
|
7981
8433
|
throw err;
|
|
7982
8434
|
} finally {
|
|
7983
8435
|
setSending(false);
|
|
7984
8436
|
}
|
|
7985
8437
|
},
|
|
7986
|
-
[client, conversationId, sending]
|
|
8438
|
+
[client, conversationId, currentUserId, sending]
|
|
7987
8439
|
);
|
|
7988
8440
|
return {
|
|
7989
8441
|
conversationId,
|
|
7990
8442
|
messages,
|
|
7991
8443
|
loading,
|
|
8444
|
+
loadingOlder,
|
|
8445
|
+
hasOlder: Boolean(nextCursor),
|
|
7992
8446
|
sending,
|
|
7993
8447
|
error,
|
|
8448
|
+
sendError,
|
|
7994
8449
|
send,
|
|
8450
|
+
loadOlder,
|
|
7995
8451
|
reload: load
|
|
7996
8452
|
};
|
|
7997
8453
|
}
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
"
|
|
8004
|
-
|
|
8454
|
+
function formatAttachError(code, labels) {
|
|
8455
|
+
switch (code) {
|
|
8456
|
+
case "too_large":
|
|
8457
|
+
return labels.attachTooLarge ?? "El archivo supera 10 MB";
|
|
8458
|
+
case "bad_type":
|
|
8459
|
+
return labels.attachBadType ?? "Solo PDF, JPEG, PNG o WebP";
|
|
8460
|
+
case "too_many":
|
|
8461
|
+
return labels.attachMaxCount ?? "M\xE1ximo 5 archivos por mensaje";
|
|
8462
|
+
default: {
|
|
8463
|
+
const _exhaustive = code;
|
|
8464
|
+
return _exhaustive;
|
|
8465
|
+
}
|
|
8005
8466
|
}
|
|
8006
|
-
return ctx;
|
|
8007
8467
|
}
|
|
8008
|
-
function
|
|
8009
|
-
|
|
8010
|
-
}
|
|
8011
|
-
function ThreadPanel({
|
|
8012
|
-
thread,
|
|
8468
|
+
function ThreadPane({
|
|
8469
|
+
item,
|
|
8013
8470
|
client,
|
|
8014
8471
|
getWsUrl,
|
|
8015
8472
|
currentUserId,
|
|
8016
8473
|
labels,
|
|
8017
8474
|
emojiLocale,
|
|
8018
|
-
onClose,
|
|
8019
|
-
onBack,
|
|
8020
8475
|
showBack,
|
|
8476
|
+
onBack,
|
|
8021
8477
|
onConversationReady,
|
|
8478
|
+
composerTools,
|
|
8479
|
+
composerAttachments,
|
|
8022
8480
|
className
|
|
8023
8481
|
}) {
|
|
8024
8482
|
const [draft, setDraft] = React2.useState("");
|
|
8025
|
-
const
|
|
8483
|
+
const [queued, setQueued] = React2.useState([]);
|
|
8484
|
+
const [attachError, setAttachError] = React2.useState(null);
|
|
8485
|
+
const fileInputRef = React2.useRef(null);
|
|
8486
|
+
const {
|
|
8487
|
+
messages,
|
|
8488
|
+
loading,
|
|
8489
|
+
loadingOlder,
|
|
8490
|
+
hasOlder,
|
|
8491
|
+
sending,
|
|
8492
|
+
error,
|
|
8493
|
+
sendError,
|
|
8494
|
+
send,
|
|
8495
|
+
loadOlder
|
|
8496
|
+
} = useSupportConversation({
|
|
8026
8497
|
client,
|
|
8027
8498
|
getWsUrl,
|
|
8028
|
-
conversationId:
|
|
8029
|
-
open:
|
|
8499
|
+
conversationId: item.conversationId ?? null,
|
|
8500
|
+
open: item.open ?? null,
|
|
8501
|
+
currentUserId,
|
|
8030
8502
|
onConversationReady
|
|
8031
8503
|
});
|
|
8504
|
+
const queuedRef = React2.useRef(queued);
|
|
8505
|
+
queuedRef.current = queued;
|
|
8506
|
+
React2.useEffect(() => {
|
|
8507
|
+
return () => {
|
|
8508
|
+
for (const q of queuedRef.current) {
|
|
8509
|
+
if (q.previewUrl) URL.revokeObjectURL(q.previewUrl);
|
|
8510
|
+
}
|
|
8511
|
+
};
|
|
8512
|
+
}, []);
|
|
8513
|
+
function onPickFiles(fileList) {
|
|
8514
|
+
if (!fileList || fileList.length === 0) return;
|
|
8515
|
+
setAttachError(null);
|
|
8516
|
+
const additions = [];
|
|
8517
|
+
for (const file of Array.from(fileList)) {
|
|
8518
|
+
if (queued.length + additions.length >= CHAT_ATTACHMENT_MAX_COUNT) {
|
|
8519
|
+
setAttachError(formatAttachError("too_many", labels));
|
|
8520
|
+
break;
|
|
8521
|
+
}
|
|
8522
|
+
const issue = validateChatAttachmentFile(file);
|
|
8523
|
+
if (issue) {
|
|
8524
|
+
setAttachError(formatAttachError(issue, labels));
|
|
8525
|
+
continue;
|
|
8526
|
+
}
|
|
8527
|
+
additions.push({
|
|
8528
|
+
id: `q:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`,
|
|
8529
|
+
file,
|
|
8530
|
+
previewUrl: isChatImageAttachment(file.type) ? URL.createObjectURL(file) : null
|
|
8531
|
+
});
|
|
8532
|
+
}
|
|
8533
|
+
if (additions.length > 0) {
|
|
8534
|
+
setQueued((prev) => [...prev, ...additions]);
|
|
8535
|
+
}
|
|
8536
|
+
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
8537
|
+
}
|
|
8538
|
+
function removeQueued(id) {
|
|
8539
|
+
setQueued((prev) => {
|
|
8540
|
+
const target = prev.find((q) => q.id === id);
|
|
8541
|
+
if (target?.previewUrl) URL.revokeObjectURL(target.previewUrl);
|
|
8542
|
+
return prev.filter((q) => q.id !== id);
|
|
8543
|
+
});
|
|
8544
|
+
setAttachError(null);
|
|
8545
|
+
}
|
|
8032
8546
|
async function onSubmit(e) {
|
|
8033
8547
|
e.preventDefault();
|
|
8034
8548
|
const text = draft.trim();
|
|
8035
|
-
|
|
8549
|
+
const files = queued.map((q) => q.file);
|
|
8550
|
+
if (!text && files.length === 0) return;
|
|
8551
|
+
const snapshot = queued;
|
|
8036
8552
|
setDraft("");
|
|
8553
|
+
setQueued([]);
|
|
8554
|
+
setAttachError(null);
|
|
8037
8555
|
try {
|
|
8038
|
-
await send(text);
|
|
8556
|
+
await send(text, files);
|
|
8557
|
+
for (const q of snapshot) {
|
|
8558
|
+
if (q.previewUrl) URL.revokeObjectURL(q.previewUrl);
|
|
8559
|
+
}
|
|
8039
8560
|
} catch {
|
|
8040
8561
|
setDraft(text);
|
|
8562
|
+
setQueued(snapshot);
|
|
8041
8563
|
}
|
|
8042
8564
|
}
|
|
8565
|
+
const previewStrip = queued.length > 0 || composerAttachments ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
8566
|
+
queued.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-wrap gap-2", children: queued.map((q) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8567
|
+
"li",
|
|
8568
|
+
{
|
|
8569
|
+
className: "bg-muted/40 relative flex max-w-[10rem] items-center gap-2 rounded-md border p-1.5",
|
|
8570
|
+
children: [
|
|
8571
|
+
q.previewUrl ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
8572
|
+
SafeImage,
|
|
8573
|
+
{
|
|
8574
|
+
src: q.previewUrl,
|
|
8575
|
+
alt: "",
|
|
8576
|
+
className: "size-10 rounded object-cover",
|
|
8577
|
+
fallback: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-muted flex size-10 items-center justify-center rounded", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "size-4 text-muted-foreground" }) })
|
|
8578
|
+
}
|
|
8579
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-muted flex size-10 items-center justify-center rounded", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "size-4 text-muted-foreground" }) }),
|
|
8580
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-[11px]", children: q.file.name }),
|
|
8581
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8582
|
+
Button,
|
|
8583
|
+
{
|
|
8584
|
+
type: "button",
|
|
8585
|
+
variant: "ghost",
|
|
8586
|
+
size: "icon-sm",
|
|
8587
|
+
className: "size-6 shrink-0",
|
|
8588
|
+
"aria-label": labels.removeAttachmentAria ?? "Quitar archivo",
|
|
8589
|
+
onClick: () => removeQueued(q.id),
|
|
8590
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-3.5" })
|
|
8591
|
+
}
|
|
8592
|
+
)
|
|
8593
|
+
]
|
|
8594
|
+
},
|
|
8595
|
+
q.id
|
|
8596
|
+
)) }) : null,
|
|
8597
|
+
composerAttachments
|
|
8598
|
+
] }) : void 0;
|
|
8043
8599
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8044
8600
|
"div",
|
|
8045
8601
|
{
|
|
8046
|
-
className: cn(
|
|
8047
|
-
"flex h-full min-h-0 flex-col overflow-hidden rounded-xl border bg-card shadow-lg",
|
|
8048
|
-
className
|
|
8049
|
-
),
|
|
8602
|
+
className: cn("flex h-full min-h-0 flex-col overflow-hidden", className),
|
|
8050
8603
|
children: [
|
|
8051
8604
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-b bg-primary/10 px-2 py-2", children: [
|
|
8052
8605
|
showBack && onBack ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -8064,72 +8617,98 @@ function ThreadPanel({
|
|
|
8064
8617
|
"span",
|
|
8065
8618
|
{
|
|
8066
8619
|
className: "flex size-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-primary text-[11px] font-semibold text-primary-foreground",
|
|
8067
|
-
style:
|
|
8620
|
+
style: item.avatarUrl ? void 0 : avatarFallbackStyle(item.title),
|
|
8068
8621
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
8069
8622
|
SafeImage,
|
|
8070
8623
|
{
|
|
8071
|
-
src:
|
|
8624
|
+
src: item.avatarUrl,
|
|
8072
8625
|
alt: "",
|
|
8073
8626
|
className: "size-full object-cover",
|
|
8074
|
-
fallback: getInitials(
|
|
8627
|
+
fallback: getInitials(item.eyebrow ?? item.title)
|
|
8075
8628
|
}
|
|
8076
8629
|
)
|
|
8077
8630
|
}
|
|
8078
8631
|
),
|
|
8079
8632
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 px-1", children: [
|
|
8080
|
-
|
|
8081
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-semibold", children:
|
|
8082
|
-
|
|
8083
|
-
] })
|
|
8084
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8085
|
-
Button,
|
|
8086
|
-
{
|
|
8087
|
-
type: "button",
|
|
8088
|
-
variant: "ghost",
|
|
8089
|
-
size: "icon-sm",
|
|
8090
|
-
onClick: onClose,
|
|
8091
|
-
"aria-label": labels.close,
|
|
8092
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4" })
|
|
8093
|
-
}
|
|
8094
|
-
)
|
|
8633
|
+
item.eyebrow ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-[11px] font-semibold tracking-wide text-primary", children: item.eyebrow }) : null,
|
|
8634
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-semibold", children: item.title }),
|
|
8635
|
+
item.subtitle ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-xs text-muted-foreground", children: item.subtitle }) : null
|
|
8636
|
+
] })
|
|
8095
8637
|
] }),
|
|
8096
|
-
/* @__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(
|
|
8638
|
+
/* @__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 && messages.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "p-4 text-sm text-destructive", role: "alert", children: error }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
8097
8639
|
ChatMessageList,
|
|
8098
8640
|
{
|
|
8099
8641
|
messages,
|
|
8100
8642
|
currentUserId,
|
|
8101
8643
|
emptyDescription: labels.emptyThread,
|
|
8102
|
-
compactEmpty: true
|
|
8644
|
+
compactEmpty: true,
|
|
8645
|
+
hasOlder,
|
|
8646
|
+
loadingOlder,
|
|
8647
|
+
onLoadOlder: () => void loadOlder(),
|
|
8648
|
+
loadOlderLabel: labels.loadOlder,
|
|
8649
|
+
loadingOlderLabel: labels.loadingOlder
|
|
8103
8650
|
}
|
|
8104
8651
|
) }),
|
|
8105
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8652
|
+
attachError || sendError ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "border-t px-3 py-2 text-sm text-destructive", role: "alert", children: attachError ?? sendError }) : null,
|
|
8653
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t p-3", children: [
|
|
8654
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8655
|
+
"input",
|
|
8656
|
+
{
|
|
8657
|
+
ref: fileInputRef,
|
|
8658
|
+
type: "file",
|
|
8659
|
+
className: "sr-only",
|
|
8660
|
+
accept: CHAT_ATTACHMENT_ACCEPT,
|
|
8661
|
+
multiple: true,
|
|
8662
|
+
onChange: (e) => onPickFiles(e.target.files)
|
|
8663
|
+
}
|
|
8664
|
+
),
|
|
8665
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8666
|
+
MessageComposer,
|
|
8667
|
+
{
|
|
8668
|
+
locale: emojiLocale,
|
|
8669
|
+
value: draft,
|
|
8670
|
+
onChange: (e) => setDraft(e.target.value.slice(0, 2e3)),
|
|
8671
|
+
onSubmit,
|
|
8672
|
+
sendLabel: labels.send,
|
|
8673
|
+
placeholder: labels.placeholder,
|
|
8674
|
+
"aria-label": labels.placeholder,
|
|
8675
|
+
disabled: loading,
|
|
8676
|
+
sendDisabled: loading || sending,
|
|
8677
|
+
allowEmptySubmit: queued.length > 0,
|
|
8678
|
+
maxLength: 2e3,
|
|
8679
|
+
rows: 2,
|
|
8680
|
+
attachments: previewStrip,
|
|
8681
|
+
tools: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
8682
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8683
|
+
Button,
|
|
8684
|
+
{
|
|
8685
|
+
type: "button",
|
|
8686
|
+
variant: "ghost",
|
|
8687
|
+
size: "icon-sm",
|
|
8688
|
+
className: "size-7 text-muted-foreground",
|
|
8689
|
+
"aria-label": labels.attachAria ?? "Adjuntar archivo",
|
|
8690
|
+
disabled: loading || sending || queued.length >= CHAT_ATTACHMENT_MAX_COUNT,
|
|
8691
|
+
onClick: () => fileInputRef.current?.click(),
|
|
8692
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "size-4" })
|
|
8693
|
+
}
|
|
8694
|
+
),
|
|
8695
|
+
composerTools
|
|
8696
|
+
] })
|
|
8697
|
+
}
|
|
8698
|
+
)
|
|
8699
|
+
] })
|
|
8121
8700
|
]
|
|
8122
8701
|
}
|
|
8123
8702
|
);
|
|
8124
8703
|
}
|
|
8125
|
-
function
|
|
8704
|
+
function ListPane({
|
|
8126
8705
|
items,
|
|
8127
8706
|
labels,
|
|
8128
8707
|
tabs,
|
|
8129
8708
|
activeTab,
|
|
8130
8709
|
onTabChange,
|
|
8710
|
+
selectedKey,
|
|
8131
8711
|
onSelect,
|
|
8132
|
-
onClose,
|
|
8133
8712
|
searchThreshold,
|
|
8134
8713
|
className
|
|
8135
8714
|
}) {
|
|
@@ -8145,26 +8724,10 @@ function ListPanel({
|
|
|
8145
8724
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8146
8725
|
"div",
|
|
8147
8726
|
{
|
|
8148
|
-
className: cn(
|
|
8149
|
-
"flex h-full min-h-0 flex-col overflow-hidden rounded-xl border bg-card shadow-lg",
|
|
8150
|
-
className
|
|
8151
|
-
),
|
|
8727
|
+
className: cn("flex h-full min-h-0 flex-col overflow-hidden", className),
|
|
8152
8728
|
children: [
|
|
8153
|
-
/* @__PURE__ */ jsxRuntime.
|
|
8154
|
-
|
|
8155
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8156
|
-
Button,
|
|
8157
|
-
{
|
|
8158
|
-
type: "button",
|
|
8159
|
-
variant: "ghost",
|
|
8160
|
-
size: "icon-sm",
|
|
8161
|
-
onClick: onClose,
|
|
8162
|
-
"aria-label": labels.close,
|
|
8163
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4" })
|
|
8164
|
-
}
|
|
8165
|
-
)
|
|
8166
|
-
] }),
|
|
8167
|
-
tabs && tabs.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 border-b p-2", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
8729
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b bg-primary/10 px-3 py-2", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold", children: labels.listTitle }) }),
|
|
8730
|
+
tabs && tabs.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 border-b p-2", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
8168
8731
|
Button,
|
|
8169
8732
|
{
|
|
8170
8733
|
type: "button",
|
|
@@ -8187,353 +8750,126 @@ function ListPanel({
|
|
|
8187
8750
|
}
|
|
8188
8751
|
)
|
|
8189
8752
|
] }) }) : null,
|
|
8190
|
-
/* @__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-4 text-center text-xs text-muted-foreground", children: labels.emptyList }) : filtered.map((item) =>
|
|
8191
|
-
|
|
8192
|
-
{
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
"
|
|
8199
|
-
{
|
|
8200
|
-
className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-full text-xs font-semibold text-white",
|
|
8201
|
-
style: avatarFallbackStyle(item.title),
|
|
8202
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
8203
|
-
SafeImage,
|
|
8204
|
-
{
|
|
8205
|
-
src: item.avatarUrl,
|
|
8206
|
-
alt: "",
|
|
8207
|
-
className: "size-full object-cover",
|
|
8208
|
-
fallback: getInitials(item.title)
|
|
8209
|
-
}
|
|
8210
|
-
)
|
|
8211
|
-
}
|
|
8753
|
+
/* @__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-4 text-center text-xs text-muted-foreground", children: labels.emptyList }) : filtered.map((item) => {
|
|
8754
|
+
const selected = item.key === selectedKey;
|
|
8755
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8756
|
+
"button",
|
|
8757
|
+
{
|
|
8758
|
+
type: "button",
|
|
8759
|
+
className: cn(
|
|
8760
|
+
"flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left transition-colors hover:bg-muted/80",
|
|
8761
|
+
selected && "bg-muted"
|
|
8212
8762
|
),
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8763
|
+
onClick: () => onSelect(item),
|
|
8764
|
+
"aria-current": selected ? "true" : void 0,
|
|
8765
|
+
children: [
|
|
8766
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8767
|
+
"span",
|
|
8768
|
+
{
|
|
8769
|
+
className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-full text-xs font-semibold text-white",
|
|
8770
|
+
style: avatarFallbackStyle(item.title),
|
|
8771
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
8772
|
+
SafeImage,
|
|
8773
|
+
{
|
|
8774
|
+
src: item.avatarUrl,
|
|
8775
|
+
alt: "",
|
|
8776
|
+
className: "size-full object-cover",
|
|
8777
|
+
fallback: getInitials(item.title)
|
|
8778
|
+
}
|
|
8779
|
+
)
|
|
8780
|
+
}
|
|
8781
|
+
),
|
|
8782
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
8783
|
+
item.eyebrow ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-[11px] font-semibold tracking-wide text-primary", children: item.eyebrow }) : null,
|
|
8784
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-sm font-medium", children: item.title }),
|
|
8785
|
+
item.subtitle ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-xs text-muted-foreground", children: item.subtitle }) : null
|
|
8786
|
+
] }),
|
|
8787
|
+
(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
|
|
8788
|
+
]
|
|
8789
|
+
}
|
|
8790
|
+
) }, item.key);
|
|
8791
|
+
}) })
|
|
8222
8792
|
]
|
|
8223
8793
|
}
|
|
8224
8794
|
);
|
|
8225
8795
|
}
|
|
8226
|
-
function
|
|
8227
|
-
children,
|
|
8796
|
+
function SupportInboxLayout({
|
|
8228
8797
|
client,
|
|
8229
8798
|
getWsUrl,
|
|
8230
8799
|
currentUserId,
|
|
8231
8800
|
items,
|
|
8232
8801
|
labels,
|
|
8233
|
-
|
|
8234
|
-
|
|
8802
|
+
selectedKey,
|
|
8803
|
+
onSelect,
|
|
8804
|
+
onBackToList,
|
|
8235
8805
|
emojiLocale = "es",
|
|
8236
8806
|
tabs,
|
|
8237
8807
|
activeTab,
|
|
8238
8808
|
onTabChange,
|
|
8239
8809
|
onRefreshItems,
|
|
8240
8810
|
searchThreshold = 8,
|
|
8241
|
-
className
|
|
8811
|
+
className,
|
|
8812
|
+
composerTools,
|
|
8813
|
+
composerAttachments
|
|
8242
8814
|
}) {
|
|
8243
8815
|
const isMobile = useMediaQuery("(max-width: 767px)");
|
|
8244
|
-
const
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
const [threads, setThreads] = React2.useState([]);
|
|
8248
|
-
const [mobileThreadKey, setMobileThreadKey] = React2.useState(null);
|
|
8249
|
-
const mobileSheetOpen = listOpen || Boolean(mobileThreadKey);
|
|
8250
|
-
const mobileSheetHeight = viewportHeight > 0 ? Math.max(240, Math.min(viewportHeight * 0.85, viewportHeight)) : void 0;
|
|
8251
|
-
const totalUnread = React2.useMemo(
|
|
8252
|
-
() => items.reduce((sum, item) => sum + (item.unreadCount ?? 0), 0),
|
|
8253
|
-
[items]
|
|
8254
|
-
);
|
|
8255
|
-
const isDesktopOpen = !isMobile && (listOpen || threads.length > 0);
|
|
8256
|
-
React2.useEffect(() => {
|
|
8257
|
-
if (!forceMinimized) return;
|
|
8258
|
-
setListOpen(false);
|
|
8259
|
-
setThreads([]);
|
|
8260
|
-
setMobileThreadKey(null);
|
|
8261
|
-
}, [forceMinimized]);
|
|
8262
|
-
const openList = React2.useCallback(() => {
|
|
8263
|
-
setListOpen(true);
|
|
8264
|
-
void onRefreshItems?.();
|
|
8265
|
-
}, [onRefreshItems]);
|
|
8266
|
-
const closeList = React2.useCallback(() => setListOpen(false), []);
|
|
8267
|
-
const toggleList = React2.useCallback(() => {
|
|
8268
|
-
setListOpen((prev) => {
|
|
8269
|
-
const next = !prev;
|
|
8270
|
-
if (next) void onRefreshItems?.();
|
|
8271
|
-
return next;
|
|
8272
|
-
});
|
|
8273
|
-
}, [onRefreshItems]);
|
|
8274
|
-
const openThread = React2.useCallback((req) => {
|
|
8275
|
-
setListOpen(false);
|
|
8276
|
-
setThreads((prev) => {
|
|
8277
|
-
const existing = prev.find((t) => t.key === req.key);
|
|
8278
|
-
if (existing) {
|
|
8279
|
-
return [
|
|
8280
|
-
...prev.filter((t) => t.key !== req.key),
|
|
8281
|
-
{ ...existing, ...req }
|
|
8282
|
-
];
|
|
8283
|
-
}
|
|
8284
|
-
const next = [...prev, req];
|
|
8285
|
-
return next.slice(-2);
|
|
8286
|
-
});
|
|
8287
|
-
setMobileThreadKey(req.key);
|
|
8288
|
-
}, []);
|
|
8289
|
-
const closeThread = React2.useCallback((key) => {
|
|
8290
|
-
setThreads((prev) => prev.filter((t) => t.key !== key));
|
|
8291
|
-
setMobileThreadKey((prev) => prev === key ? null : prev);
|
|
8292
|
-
}, []);
|
|
8293
|
-
const closeAll = React2.useCallback(() => {
|
|
8294
|
-
setListOpen(false);
|
|
8295
|
-
setThreads([]);
|
|
8296
|
-
setMobileThreadKey(null);
|
|
8297
|
-
}, []);
|
|
8298
|
-
React2.useEffect(() => {
|
|
8299
|
-
if (!isDesktopOpen) return;
|
|
8300
|
-
function onPointerDown(event) {
|
|
8301
|
-
const target = event.target;
|
|
8302
|
-
if (!(target instanceof Node)) return;
|
|
8303
|
-
if (dockRef.current?.contains(target)) return;
|
|
8304
|
-
if (target instanceof Element && target.closest('[data-slot="popover-content"]')) {
|
|
8305
|
-
return;
|
|
8306
|
-
}
|
|
8307
|
-
closeAll();
|
|
8308
|
-
}
|
|
8309
|
-
document.addEventListener("pointerdown", onPointerDown);
|
|
8310
|
-
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
8311
|
-
}, [isDesktopOpen, closeAll]);
|
|
8312
|
-
const ctx = React2.useMemo(
|
|
8313
|
-
() => ({
|
|
8314
|
-
listOpen,
|
|
8315
|
-
openList,
|
|
8316
|
-
closeList,
|
|
8317
|
-
toggleList,
|
|
8318
|
-
openThread,
|
|
8319
|
-
closeThread,
|
|
8320
|
-
closeAll,
|
|
8321
|
-
activeThreadKey: mobileThreadKey ?? threads[threads.length - 1]?.key ?? null,
|
|
8322
|
-
totalUnread
|
|
8323
|
-
}),
|
|
8324
|
-
[
|
|
8325
|
-
listOpen,
|
|
8326
|
-
openList,
|
|
8327
|
-
closeList,
|
|
8328
|
-
toggleList,
|
|
8329
|
-
openThread,
|
|
8330
|
-
closeThread,
|
|
8331
|
-
closeAll,
|
|
8332
|
-
mobileThreadKey,
|
|
8333
|
-
threads,
|
|
8334
|
-
totalUnread
|
|
8335
|
-
]
|
|
8336
|
-
);
|
|
8337
|
-
const handleSelectItem = React2.useCallback(
|
|
8338
|
-
(item) => {
|
|
8339
|
-
openThread({
|
|
8340
|
-
key: item.key,
|
|
8341
|
-
title: item.title,
|
|
8342
|
-
subtitle: item.subtitle,
|
|
8343
|
-
eyebrow: item.eyebrow,
|
|
8344
|
-
avatarUrl: item.avatarUrl,
|
|
8345
|
-
conversationId: item.conversationId,
|
|
8346
|
-
open: item.open
|
|
8347
|
-
});
|
|
8348
|
-
},
|
|
8349
|
-
[openThread]
|
|
8816
|
+
const selected = React2.useMemo(
|
|
8817
|
+
() => items.find((item) => item.key === selectedKey) ?? null,
|
|
8818
|
+
[items, selectedKey]
|
|
8350
8819
|
);
|
|
8351
|
-
const
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
className
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
currentUserId,
|
|
8371
|
-
labels,
|
|
8372
|
-
emojiLocale,
|
|
8373
|
-
onClose: () => closeThread(thread.key),
|
|
8374
|
-
onConversationReady: () => void onRefreshItems?.()
|
|
8375
|
-
}
|
|
8376
|
-
) }, thread.key)),
|
|
8377
|
-
listOpen ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[28rem] w-[20rem]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
8378
|
-
ListPanel,
|
|
8820
|
+
const showList = !isMobile || !selected;
|
|
8821
|
+
const showThread = Boolean(selected) && (!isMobile || Boolean(selectedKey));
|
|
8822
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8823
|
+
"div",
|
|
8824
|
+
{
|
|
8825
|
+
className: cn(
|
|
8826
|
+
"flex h-full min-h-0 overflow-hidden rounded-xl border bg-card",
|
|
8827
|
+
className
|
|
8828
|
+
),
|
|
8829
|
+
children: [
|
|
8830
|
+
showList ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
8831
|
+
"div",
|
|
8832
|
+
{
|
|
8833
|
+
className: cn(
|
|
8834
|
+
"min-h-0 min-w-0 border-r",
|
|
8835
|
+
isMobile ? "w-full" : "w-full max-w-sm shrink-0"
|
|
8836
|
+
),
|
|
8837
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
8838
|
+
ListPane,
|
|
8379
8839
|
{
|
|
8380
8840
|
items,
|
|
8381
8841
|
labels,
|
|
8382
8842
|
tabs,
|
|
8383
8843
|
activeTab,
|
|
8384
8844
|
onTabChange,
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
searchThreshold
|
|
8845
|
+
selectedKey,
|
|
8846
|
+
onSelect,
|
|
8847
|
+
searchThreshold,
|
|
8848
|
+
className: "h-full"
|
|
8388
8849
|
}
|
|
8389
|
-
)
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
/* @__PURE__ */ jsxRuntime.jsx(DrawerHeader, { className: "sr-only", children: /* @__PURE__ */ jsxRuntime.jsx(DrawerTitle, { children: activeMobileThread.title }) }),
|
|
8411
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8412
|
-
ThreadPanel,
|
|
8413
|
-
{
|
|
8414
|
-
thread: activeMobileThread,
|
|
8415
|
-
client,
|
|
8416
|
-
getWsUrl,
|
|
8417
|
-
currentUserId,
|
|
8418
|
-
labels,
|
|
8419
|
-
emojiLocale,
|
|
8420
|
-
showBack: true,
|
|
8421
|
-
onBack: () => {
|
|
8422
|
-
closeThread(activeMobileThread.key);
|
|
8423
|
-
setListOpen(true);
|
|
8424
|
-
},
|
|
8425
|
-
onClose: closeAll,
|
|
8426
|
-
onConversationReady: () => void onRefreshItems?.(),
|
|
8427
|
-
className: "h-full rounded-none border-0 shadow-none"
|
|
8428
|
-
}
|
|
8429
|
-
)
|
|
8430
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
8431
|
-
/* @__PURE__ */ jsxRuntime.jsx(DrawerHeader, { className: "sr-only", children: /* @__PURE__ */ jsxRuntime.jsx(DrawerTitle, { children: labels.listTitle }) }),
|
|
8432
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8433
|
-
ListPanel,
|
|
8434
|
-
{
|
|
8435
|
-
items,
|
|
8436
|
-
labels,
|
|
8437
|
-
tabs,
|
|
8438
|
-
activeTab,
|
|
8439
|
-
onTabChange,
|
|
8440
|
-
onSelect: handleSelectItem,
|
|
8441
|
-
onClose: closeAll,
|
|
8442
|
-
searchThreshold,
|
|
8443
|
-
className: "h-full rounded-none border-0 shadow-none"
|
|
8444
|
-
}
|
|
8445
|
-
)
|
|
8446
|
-
] })
|
|
8447
|
-
}
|
|
8448
|
-
)
|
|
8449
|
-
}
|
|
8450
|
-
) : null,
|
|
8451
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
8452
|
-
"button",
|
|
8453
|
-
{
|
|
8454
|
-
type: "button",
|
|
8455
|
-
className: cn(
|
|
8456
|
-
"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",
|
|
8457
|
-
listOpen && !forceMinimized && "ring-2 ring-ring ring-offset-2"
|
|
8458
|
-
),
|
|
8459
|
-
"aria-label": totalUnread > 0 && labels.unreadBadgeAria ? `${labels.launcherAria}. ${labels.unreadBadgeAria.replace("{count}", String(totalUnread))}` : labels.launcherAria,
|
|
8460
|
-
onClick: () => {
|
|
8461
|
-
if (isMobile) {
|
|
8462
|
-
if (activeMobileThread) {
|
|
8463
|
-
closeAll();
|
|
8464
|
-
} else {
|
|
8465
|
-
toggleList();
|
|
8466
|
-
}
|
|
8467
|
-
return;
|
|
8468
|
-
}
|
|
8469
|
-
toggleList();
|
|
8470
|
-
},
|
|
8471
|
-
children: [
|
|
8472
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageCircle, { className: "size-6" }),
|
|
8473
|
-
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
|
|
8474
|
-
]
|
|
8475
|
-
}
|
|
8476
|
-
)
|
|
8477
|
-
]
|
|
8478
|
-
}
|
|
8479
|
-
) : null
|
|
8480
|
-
] });
|
|
8481
|
-
}
|
|
8482
|
-
|
|
8483
|
-
// src/lib/support-chat.ts
|
|
8484
|
-
async function parseJson(res) {
|
|
8485
|
-
const body = await res.json();
|
|
8486
|
-
if (!res.ok) {
|
|
8487
|
-
throw new Error(
|
|
8488
|
-
typeof body === "object" && body && "error" in body && body.error ? String(body.error) : `Chat request failed (${res.status})`
|
|
8489
|
-
);
|
|
8490
|
-
}
|
|
8491
|
-
return body;
|
|
8492
|
-
}
|
|
8493
|
-
function createSupportChatClient(basePath = "/api/chat-svc") {
|
|
8494
|
-
const root = basePath.replace(/\/$/, "");
|
|
8495
|
-
return {
|
|
8496
|
-
basePath: root,
|
|
8497
|
-
async listConversations() {
|
|
8498
|
-
const res = await fetch(`${root}/me/conversations`);
|
|
8499
|
-
const body = await parseJson(res);
|
|
8500
|
-
return body.result ?? [];
|
|
8501
|
-
},
|
|
8502
|
-
async findOrCreateConversation(input) {
|
|
8503
|
-
const res = await fetch(`${root}/conversations`, {
|
|
8504
|
-
method: "POST",
|
|
8505
|
-
headers: { "content-type": "application/json" },
|
|
8506
|
-
body: JSON.stringify(input)
|
|
8507
|
-
});
|
|
8508
|
-
const body = await parseJson(res);
|
|
8509
|
-
return body.result;
|
|
8510
|
-
},
|
|
8511
|
-
async listMessages(conversationId) {
|
|
8512
|
-
const res = await fetch(
|
|
8513
|
-
`${root}/conversations/${encodeURIComponent(conversationId)}/messages`
|
|
8514
|
-
);
|
|
8515
|
-
const body = await parseJson(res);
|
|
8516
|
-
return body.result ?? [];
|
|
8517
|
-
},
|
|
8518
|
-
async sendMessage(conversationId, text) {
|
|
8519
|
-
const res = await fetch(
|
|
8520
|
-
`${root}/conversations/${encodeURIComponent(conversationId)}/messages`,
|
|
8521
|
-
{
|
|
8522
|
-
method: "POST",
|
|
8523
|
-
headers: { "content-type": "application/json" },
|
|
8524
|
-
body: JSON.stringify({ body: text })
|
|
8525
|
-
}
|
|
8526
|
-
);
|
|
8527
|
-
const body = await parseJson(res);
|
|
8528
|
-
return body.result;
|
|
8529
|
-
},
|
|
8530
|
-
async markRead(conversationId) {
|
|
8531
|
-
await fetch(
|
|
8532
|
-
`${root}/conversations/${encodeURIComponent(conversationId)}/read`,
|
|
8533
|
-
{ method: "POST" }
|
|
8534
|
-
);
|
|
8850
|
+
)
|
|
8851
|
+
}
|
|
8852
|
+
) : null,
|
|
8853
|
+
showThread && selected ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
8854
|
+
ThreadPane,
|
|
8855
|
+
{
|
|
8856
|
+
item: selected,
|
|
8857
|
+
client,
|
|
8858
|
+
getWsUrl,
|
|
8859
|
+
currentUserId,
|
|
8860
|
+
labels,
|
|
8861
|
+
emojiLocale,
|
|
8862
|
+
showBack: isMobile,
|
|
8863
|
+
onBack: onBackToList,
|
|
8864
|
+
onConversationReady: () => void onRefreshItems?.(),
|
|
8865
|
+
composerTools,
|
|
8866
|
+
composerAttachments,
|
|
8867
|
+
className: "h-full"
|
|
8868
|
+
}
|
|
8869
|
+
) }) : !isMobile ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center p-6 text-center text-sm text-muted-foreground", children: labels.selectThread ?? labels.emptyThread }) : null
|
|
8870
|
+
]
|
|
8535
8871
|
}
|
|
8536
|
-
|
|
8872
|
+
);
|
|
8537
8873
|
}
|
|
8538
8874
|
var DEFAULT_RECONNECT_MS = 2e3;
|
|
8539
8875
|
var MAX_RECONNECT_MS = 3e4;
|
|
@@ -9394,6 +9730,10 @@ exports.Bubble = Bubble;
|
|
|
9394
9730
|
exports.BubbleContent = BubbleContent;
|
|
9395
9731
|
exports.BubbleGroup = BubbleGroup;
|
|
9396
9732
|
exports.BubbleReactions = BubbleReactions;
|
|
9733
|
+
exports.CHAT_ATTACHMENT_ACCEPT = CHAT_ATTACHMENT_ACCEPT;
|
|
9734
|
+
exports.CHAT_ATTACHMENT_MAX_BYTES = CHAT_ATTACHMENT_MAX_BYTES;
|
|
9735
|
+
exports.CHAT_ATTACHMENT_MAX_COUNT = CHAT_ATTACHMENT_MAX_COUNT;
|
|
9736
|
+
exports.CHAT_ATTACHMENT_MIME_TYPES = CHAT_ATTACHMENT_MIME_TYPES;
|
|
9397
9737
|
exports.ChatMessageList = ChatMessageList;
|
|
9398
9738
|
exports.ConfirmDestructiveDialog = ConfirmDestructiveDialog;
|
|
9399
9739
|
exports.DEFAULT_EDITABLE_SECTION_LABELS = DEFAULT_EDITABLE_SECTION_LABELS;
|
|
@@ -9456,6 +9796,7 @@ exports.SafeImage = SafeImage;
|
|
|
9456
9796
|
exports.ScrollRestorationProvider = ScrollRestorationProvider;
|
|
9457
9797
|
exports.SearchCommand = SearchCommand;
|
|
9458
9798
|
exports.SearchablePicker = SearchablePicker;
|
|
9799
|
+
exports.SupportInboxLayout = SupportInboxLayout;
|
|
9459
9800
|
exports.SupportMessengerProvider = SupportMessengerProvider;
|
|
9460
9801
|
exports.ThemeSwitcher = ThemeSwitcher;
|
|
9461
9802
|
exports.VerifiedProfessionalBadge = VerifiedProfessionalBadge;
|
|
@@ -9469,12 +9810,15 @@ exports.bubbleVariants = bubbleVariants;
|
|
|
9469
9810
|
exports.cn = cn;
|
|
9470
9811
|
exports.createSupportChatClient = createSupportChatClient;
|
|
9471
9812
|
exports.defaultLanguages = defaultLanguages;
|
|
9813
|
+
exports.filenameMatchesChatMime = filenameMatchesChatMime;
|
|
9472
9814
|
exports.formatPaymentCardBrand = formatPaymentCardBrand;
|
|
9473
9815
|
exports.formatRfcInput = formatRfcInput;
|
|
9474
9816
|
exports.fuzzyMatch = fuzzyMatch;
|
|
9475
9817
|
exports.getAvatarColors = getAvatarColors;
|
|
9476
9818
|
exports.getInitials = getInitials;
|
|
9477
9819
|
exports.insertAtCaret = insertAtCaret;
|
|
9820
|
+
exports.isChatAttachmentMime = isChatAttachmentMime;
|
|
9821
|
+
exports.isChatImageAttachment = isChatImageAttachment;
|
|
9478
9822
|
exports.isValidPersonaFisicaRfc = isValidPersonaFisicaRfc;
|
|
9479
9823
|
exports.isValidPersonaMoralRfc = isValidPersonaMoralRfc;
|
|
9480
9824
|
exports.isValidRfc = isValidRfc;
|
|
@@ -9503,6 +9847,7 @@ exports.useSupportMessenger = useSupportMessenger;
|
|
|
9503
9847
|
exports.useSupportMessengerOptional = useSupportMessengerOptional;
|
|
9504
9848
|
exports.useVisualViewportMetrics = useVisualViewportMetrics;
|
|
9505
9849
|
exports.useZoomPanGestures = useZoomPanGestures;
|
|
9850
|
+
exports.validateChatAttachmentFile = validateChatAttachmentFile;
|
|
9506
9851
|
exports.zoomToward = zoomToward;
|
|
9507
9852
|
//# sourceMappingURL=index.cjs.map
|
|
9508
9853
|
//# sourceMappingURL=index.cjs.map
|