@adatechnology/conversations-ui 0.1.0-rc.2 → 0.1.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/chunk-4R6Y43DQ.js +726 -0
- package/dist/chunk-NV2RZ5KT.js +56 -0
- package/dist/{chunk-ZDURDZTM.js → chunk-OGRRHQQW.js} +1 -41
- package/dist/flows/index.js +6 -4
- package/dist/index.d.ts +323 -111
- package/dist/index.js +1032 -954
- package/dist/preview/index.d.ts +172 -0
- package/dist/preview/index.js +576 -0
- package/dist/styles.css +198 -0
- package/dist/types-C0PtaO7S.d.ts +207 -0
- package/package.json +10 -3
- package/src/Avatar.tsx +18 -3
- package/src/ChannelIcon.tsx +87 -0
- package/src/ConversationContextPanel.tsx +106 -0
- package/src/ConversationDocumentsPanel.tsx +107 -0
- package/src/ConversationHeader.tsx +239 -0
- package/src/ConversationListItem.tsx +36 -5
- package/src/ConversationLocalesProvider.tsx +16 -0
- package/src/ConversationRow.tsx +137 -0
- package/src/DateDivider.tsx +16 -3
- package/src/MediaRenderer.tsx +9 -9
- package/src/MessageBubble.tsx +24 -2
- package/src/MessageComposer.tsx +15 -2
- package/src/Wallpaper.tsx +4 -2
- package/src/WindowExpiredNotice.tsx +57 -0
- package/src/conversationChannel.test.ts +53 -0
- package/src/conversationChannel.ts +146 -0
- package/src/conversationTranscript.test.ts +65 -0
- package/src/conversationTranscript.ts +64 -0
- package/src/conversationWindow.test.ts +90 -0
- package/src/conversationWindow.ts +78 -0
- package/src/flows/FlowMapCanvas.tsx +2 -2
- package/src/hooks/useConversationDocuments.ts +4 -2
- package/src/index.ts +73 -4
- package/src/lib/cn.ts +15 -0
- package/src/lib/phone.ts +34 -0
- package/src/preview/ConversationPreview.tsx +148 -0
- package/src/preview/createMockConversationsApi.ts +111 -0
- package/src/preview/createMockSSEProvider.ts +40 -0
- package/src/preview/createPreviewWebhookClient.test.ts +105 -0
- package/src/preview/createPreviewWebhookClient.ts +99 -0
- package/src/preview/index.ts +40 -0
- package/src/preview/mockEventSource.ts +53 -0
- package/src/preview/preview.test.ts +175 -0
- package/src/preview/previewFixtures.ts +153 -0
- package/src/preview/previewStore.ts +193 -0
- package/src/preview/startPreviewScript.ts +60 -0
- package/src/providers/types.ts +36 -2
- package/src/settings/WhatsAppTemplatesSettings.tsx +106 -0
- package/src/styles.css +136 -0
- package/src/types.ts +8 -0
- package/src/useDarkMode.ts +26 -0
- package/src/useIsNarrow.ts +29 -0
package/dist/index.js
CHANGED
|
@@ -1,652 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useDarkMode,
|
|
3
|
+
useIsDarkTheme
|
|
4
|
+
} from "./chunk-NV2RZ5KT.js";
|
|
5
|
+
import {
|
|
6
|
+
AudioPlayer,
|
|
7
|
+
ConversationLocalesProvider,
|
|
8
|
+
ConversationWallpaper,
|
|
9
|
+
DateDivider,
|
|
10
|
+
EmojiPicker,
|
|
11
|
+
FileIcon,
|
|
12
|
+
Lightbox,
|
|
13
|
+
MediaRenderer,
|
|
14
|
+
MessageBubble,
|
|
15
|
+
MessageComposer,
|
|
16
|
+
StatusTicks,
|
|
17
|
+
cn,
|
|
18
|
+
formatFileSize,
|
|
19
|
+
formatTimestamp,
|
|
20
|
+
useConversationLocales
|
|
21
|
+
} from "./chunk-4R6Y43DQ.js";
|
|
1
22
|
import {
|
|
2
23
|
htmlToWA,
|
|
3
24
|
parseWhatsAppFormatting,
|
|
4
|
-
useDarkMode,
|
|
5
25
|
waToHTML,
|
|
6
26
|
waToHTMLInline
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
|
|
9
|
-
// src/MessageBubble.tsx
|
|
10
|
-
import { useState as useState3 } from "react";
|
|
11
|
-
import { Check } from "lucide-react";
|
|
12
|
-
|
|
13
|
-
// src/ConversationLocalesProvider.tsx
|
|
14
|
-
import { createContext, useContext } from "react";
|
|
15
|
-
import { jsx } from "react/jsx-runtime";
|
|
16
|
-
var DEFAULT_LOCALES = {
|
|
17
|
-
bubble: {
|
|
18
|
-
customer: "Cliente",
|
|
19
|
-
bot: "Bot",
|
|
20
|
-
agent: "Atendente",
|
|
21
|
-
document: "documento",
|
|
22
|
-
media: "m\xEDdia",
|
|
23
|
-
templateLabel: "Template",
|
|
24
|
-
readAt: "Lida \xE0s ",
|
|
25
|
-
windowExpired: "Janela de 24h expirada",
|
|
26
|
-
viewImage: "Ver imagem",
|
|
27
|
-
listenAudio: "Ouvir \xE1udio",
|
|
28
|
-
viewVideo: "Ver v\xEDdeo"
|
|
29
|
-
},
|
|
30
|
-
selection: {
|
|
31
|
-
select: "Selecionar"
|
|
32
|
-
},
|
|
33
|
-
dateDivider: {
|
|
34
|
-
today: "Hoje",
|
|
35
|
-
yesterday: "Ontem"
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
var ConversationLocalesContext = createContext(DEFAULT_LOCALES);
|
|
39
|
-
function ConversationLocalesProvider({ children, locales }) {
|
|
40
|
-
const merged = {
|
|
41
|
-
bubble: { ...DEFAULT_LOCALES.bubble, ...locales?.bubble },
|
|
42
|
-
selection: { ...DEFAULT_LOCALES.selection, ...locales?.selection },
|
|
43
|
-
dateDivider: { ...DEFAULT_LOCALES.dateDivider, ...locales?.dateDivider }
|
|
44
|
-
};
|
|
45
|
-
return /* @__PURE__ */ jsx(ConversationLocalesContext.Provider, { value: merged, children });
|
|
46
|
-
}
|
|
47
|
-
function useConversationLocales() {
|
|
48
|
-
return useContext(ConversationLocalesContext);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// src/StatusTicks.tsx
|
|
52
|
-
import { AlertTriangle } from "lucide-react";
|
|
53
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
54
|
-
var STATUS_COLOR_CLASS = {
|
|
55
|
-
sent: "text-black/40 dark:text-white/40",
|
|
56
|
-
delivered: "text-black/40 dark:text-white/40",
|
|
57
|
-
read: "text-sky-500",
|
|
58
|
-
failed: "text-red-500"
|
|
59
|
-
};
|
|
60
|
-
function Ticks({ double }) {
|
|
61
|
-
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 20 12", width: "15", height: "9", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
|
|
62
|
-
double && /* @__PURE__ */ jsx2("path", { d: "M1 6.5L4.5 10L11 2", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }),
|
|
63
|
-
/* @__PURE__ */ jsx2(
|
|
64
|
-
"path",
|
|
65
|
-
{
|
|
66
|
-
d: double ? "M6 6.5L9.5 10L19 1" : "M1 6.5L5 10.5L14.5 1",
|
|
67
|
-
stroke: "currentColor",
|
|
68
|
-
strokeWidth: "1.6",
|
|
69
|
-
strokeLinecap: "round",
|
|
70
|
-
strokeLinejoin: "round"
|
|
71
|
-
}
|
|
72
|
-
)
|
|
73
|
-
] });
|
|
74
|
-
}
|
|
75
|
-
function StatusTicks({ status, title }) {
|
|
76
|
-
const colorClass = STATUS_COLOR_CLASS[status] ?? STATUS_COLOR_CLASS.sent;
|
|
77
|
-
return /* @__PURE__ */ jsx2("span", { className: `cursor-help leading-none flex items-center ${colorClass}`, title, children: status === "failed" ? /* @__PURE__ */ jsx2(AlertTriangle, { size: 11 }) : /* @__PURE__ */ jsx2(Ticks, { double: status !== "sent" }) });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// src/MediaRenderer.tsx
|
|
81
|
-
import { useState as useState2 } from "react";
|
|
82
|
-
|
|
83
|
-
// src/AudioPlayer.tsx
|
|
84
|
-
import { useEffect, useRef, useState } from "react";
|
|
85
|
-
import { Pause, Play } from "lucide-react";
|
|
86
|
-
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
87
|
-
var BARS = Array.from({ length: 30 }, (_, i) => {
|
|
88
|
-
const heights = [3, 5, 8, 12, 7, 10, 14, 9, 6, 11, 15, 8, 4, 13, 7, 10, 6, 12, 9, 5, 14, 8, 11, 6, 13, 7, 10, 5, 9, 4];
|
|
89
|
-
return heights[i % heights.length];
|
|
90
|
-
});
|
|
91
|
-
function fmt(sec) {
|
|
92
|
-
if (!isFinite(sec)) return "0:00";
|
|
93
|
-
const m = Math.floor(sec / 60);
|
|
94
|
-
const s = Math.floor(sec % 60);
|
|
95
|
-
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
96
|
-
}
|
|
97
|
-
function AudioPlayer({ src, isMine = false }) {
|
|
98
|
-
const audioRef = useRef(null);
|
|
99
|
-
const [playing, setPlaying] = useState(false);
|
|
100
|
-
const [progress, setProgress] = useState(0);
|
|
101
|
-
const [duration, setDuration] = useState(0);
|
|
102
|
-
const [current, setCurrent] = useState(0);
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
const audio = audioRef.current;
|
|
105
|
-
if (!audio) return;
|
|
106
|
-
const onTime = () => {
|
|
107
|
-
setCurrent(audio.currentTime);
|
|
108
|
-
setProgress(audio.duration ? audio.currentTime / audio.duration : 0);
|
|
109
|
-
};
|
|
110
|
-
const onMeta = () => setDuration(audio.duration);
|
|
111
|
-
const onEnd = () => {
|
|
112
|
-
setPlaying(false);
|
|
113
|
-
setProgress(0);
|
|
114
|
-
setCurrent(0);
|
|
115
|
-
};
|
|
116
|
-
audio.addEventListener("timeupdate", onTime);
|
|
117
|
-
audio.addEventListener("loadedmetadata", onMeta);
|
|
118
|
-
audio.addEventListener("ended", onEnd);
|
|
119
|
-
return () => {
|
|
120
|
-
audio.removeEventListener("timeupdate", onTime);
|
|
121
|
-
audio.removeEventListener("loadedmetadata", onMeta);
|
|
122
|
-
audio.removeEventListener("ended", onEnd);
|
|
123
|
-
};
|
|
124
|
-
}, []);
|
|
125
|
-
const toggle = () => {
|
|
126
|
-
const audio = audioRef.current;
|
|
127
|
-
if (!audio) return;
|
|
128
|
-
if (playing) {
|
|
129
|
-
audio.pause();
|
|
130
|
-
setPlaying(false);
|
|
131
|
-
} else {
|
|
132
|
-
audio.play();
|
|
133
|
-
setPlaying(true);
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
const seek = (e) => {
|
|
137
|
-
const audio = audioRef.current;
|
|
138
|
-
if (!audio || !audio.duration) return;
|
|
139
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
140
|
-
const ratio = (e.clientX - rect.left) / rect.width;
|
|
141
|
-
audio.currentTime = ratio * audio.duration;
|
|
142
|
-
};
|
|
143
|
-
const activeBars = Math.round(progress * BARS.length);
|
|
144
|
-
const playBtn = isMine ? "bg-green-600 hover:bg-green-700 text-white" : "bg-gray-600 hover:bg-gray-700 text-white";
|
|
145
|
-
const activeBar = isMine ? "bg-green-700" : "bg-gray-700";
|
|
146
|
-
const inactiveBar = isMine ? "bg-green-300" : "bg-gray-300";
|
|
147
|
-
return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 w-full", style: { minWidth: "200px", maxWidth: "260px" }, children: [
|
|
148
|
-
/* @__PURE__ */ jsx3("audio", { ref: audioRef, src, preload: "metadata" }),
|
|
149
|
-
/* @__PURE__ */ jsx3(
|
|
150
|
-
"button",
|
|
151
|
-
{
|
|
152
|
-
onClick: toggle,
|
|
153
|
-
className: `flex-shrink-0 w-9 h-9 rounded-full flex items-center justify-center transition-colors ${playBtn}`,
|
|
154
|
-
children: playing ? /* @__PURE__ */ jsx3(Pause, { size: 16 }) : /* @__PURE__ */ jsx3(Play, { size: 16, className: "translate-x-0.5" })
|
|
155
|
-
}
|
|
156
|
-
),
|
|
157
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-1", children: [
|
|
158
|
-
/* @__PURE__ */ jsx3("div", { className: "flex items-end gap-0.5 h-8 cursor-pointer", onClick: seek, children: BARS.map((h, i) => /* @__PURE__ */ jsx3(
|
|
159
|
-
"div",
|
|
160
|
-
{
|
|
161
|
-
className: `flex-1 rounded-full transition-colors ${i < activeBars ? activeBar : inactiveBar}`,
|
|
162
|
-
style: { height: `${h * 2}px` }
|
|
163
|
-
},
|
|
164
|
-
i
|
|
165
|
-
)) }),
|
|
166
|
-
/* @__PURE__ */ jsx3("span", { className: "text-gray-400 tabular-nums text-xs", children: playing || current > 0 ? fmt(current) : fmt(duration) })
|
|
167
|
-
] })
|
|
168
|
-
] });
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// src/FileIcon.tsx
|
|
172
|
-
import { FileArchive, FileSpreadsheet, FileText, File as FileGeneric } from "lucide-react";
|
|
173
|
-
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
174
|
-
var EXTENSION_STYLE = {
|
|
175
|
-
pdf: { Icon: FileText, colorClass: "text-red-500" },
|
|
176
|
-
doc: { Icon: FileText, colorClass: "text-blue-500" },
|
|
177
|
-
docx: { Icon: FileText, colorClass: "text-blue-500" },
|
|
178
|
-
xls: { Icon: FileSpreadsheet, colorClass: "text-green-600" },
|
|
179
|
-
xlsx: { Icon: FileSpreadsheet, colorClass: "text-green-600" },
|
|
180
|
-
zip: { Icon: FileArchive, colorClass: "text-orange-500" }
|
|
181
|
-
};
|
|
182
|
-
function getExtension(filename, mimeType) {
|
|
183
|
-
const fromFilename = filename?.split(".").pop()?.toLowerCase();
|
|
184
|
-
if (fromFilename && EXTENSION_STYLE[fromFilename]) return fromFilename;
|
|
185
|
-
return mimeType?.split("/")[1]?.toLowerCase() ?? "";
|
|
186
|
-
}
|
|
187
|
-
function FileIcon({ filename, mimeType, size = 20, className = "" }) {
|
|
188
|
-
const extension = getExtension(filename, mimeType);
|
|
189
|
-
const style = EXTENSION_STYLE[extension] ?? { Icon: FileGeneric, colorClass: "text-gray-500" };
|
|
190
|
-
const { Icon, colorClass } = style;
|
|
191
|
-
return /* @__PURE__ */ jsx4(Icon, { size, className: `${colorClass} ${className}`.trim() });
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// src/lib/format.ts
|
|
195
|
-
function formatTimestamp(timestamp) {
|
|
196
|
-
try {
|
|
197
|
-
const date = new Date(timestamp);
|
|
198
|
-
const hours = date.getHours().toString().padStart(2, "0");
|
|
199
|
-
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
200
|
-
return `${hours}:${minutes}`;
|
|
201
|
-
} catch {
|
|
202
|
-
return timestamp;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
function formatDateTime(iso) {
|
|
206
|
-
const d = new Date(iso);
|
|
207
|
-
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
208
|
-
}
|
|
209
|
-
function isSameDay(a, b) {
|
|
210
|
-
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
211
|
-
}
|
|
212
|
-
var FILE_SIZE_UNITS = ["B", "KB", "MB", "GB"];
|
|
213
|
-
function formatFileSize(bytes) {
|
|
214
|
-
if (!isFinite(bytes) || bytes < 0) return "";
|
|
215
|
-
if (bytes < 1) return "0 B";
|
|
216
|
-
const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), FILE_SIZE_UNITS.length - 1);
|
|
217
|
-
const value = bytes / 1024 ** exponent;
|
|
218
|
-
const formatted = exponent === 0 ? value.toString() : value.toFixed(value < 10 ? 1 : 0);
|
|
219
|
-
return `${formatted} ${FILE_SIZE_UNITS[exponent]}`;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// src/MediaRenderer.tsx
|
|
223
|
-
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
224
|
-
function resolveMediaSource(message) {
|
|
225
|
-
if (message.mediaUrl) return message.mediaUrl;
|
|
226
|
-
if (message.base64) {
|
|
227
|
-
const prefix = message.mimeType ? `data:${message.mimeType};base64,` : "data:application/octet-stream;base64,";
|
|
228
|
-
return prefix + message.base64;
|
|
229
|
-
}
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
function hasLazyRef(message) {
|
|
233
|
-
return Boolean(message.uploadId || message.mediaId);
|
|
234
|
-
}
|
|
235
|
-
function useLazyMediaUrl(message, onResolveUrl) {
|
|
236
|
-
const [url, setUrl] = useState2(null);
|
|
237
|
-
const [loading, setLoading] = useState2(false);
|
|
238
|
-
const [error, setError] = useState2(false);
|
|
239
|
-
const load = async () => {
|
|
240
|
-
if (url || loading || !onResolveUrl) return;
|
|
241
|
-
setLoading(true);
|
|
242
|
-
setError(false);
|
|
243
|
-
try {
|
|
244
|
-
const resolved = await onResolveUrl(message);
|
|
245
|
-
if (resolved) setUrl(resolved);
|
|
246
|
-
else setError(true);
|
|
247
|
-
} catch {
|
|
248
|
-
setError(true);
|
|
249
|
-
} finally {
|
|
250
|
-
setLoading(false);
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
return { url, loading, error, load };
|
|
254
|
-
}
|
|
255
|
-
function MediaRenderer({ message, onLightbox, onResolveUrl }) {
|
|
256
|
-
const { bubble } = useConversationLocales();
|
|
257
|
-
const eagerSrc = resolveMediaSource(message);
|
|
258
|
-
const lazy = useLazyMediaUrl(message, onResolveUrl);
|
|
259
|
-
const src = eagerSrc ?? lazy.url;
|
|
260
|
-
const canLazyLoad = !eagerSrc && hasLazyRef(message) && Boolean(onResolveUrl);
|
|
261
|
-
const lazyButtonClass = "text-xs text-blue-600 underline flex items-center gap-1";
|
|
262
|
-
switch (message.type) {
|
|
263
|
-
case "image":
|
|
264
|
-
case "sticker": {
|
|
265
|
-
if (!src && canLazyLoad) {
|
|
266
|
-
return /* @__PURE__ */ jsx5("button", { onClick: lazy.load, className: lazyButtonClass, children: lazy.loading ? "Carregando..." : lazy.error ? "Erro \u2014 tentar novamente" : bubble.viewImage });
|
|
267
|
-
}
|
|
268
|
-
return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5("img", { src, alt: message.caption ?? "Image", className: "w-full max-h-80 object-cover cursor-pointer hover:opacity-90 transition-opacity", onClick: () => onLightbox(src), loading: "lazy" }) : /* @__PURE__ */ jsx5("div", { className: "w-full h-40 bg-gray-200 flex items-center justify-center text-gray-400", children: /* @__PURE__ */ jsxs3("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
|
|
269
|
-
/* @__PURE__ */ jsx5("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
|
|
270
|
-
/* @__PURE__ */ jsx5("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
|
|
271
|
-
/* @__PURE__ */ jsx5("polyline", { points: "21 15 16 10 5 21" })
|
|
272
|
-
] }) }) });
|
|
273
|
-
}
|
|
274
|
-
case "video": {
|
|
275
|
-
if (!src && canLazyLoad) {
|
|
276
|
-
return /* @__PURE__ */ jsx5("button", { onClick: lazy.load, className: lazyButtonClass, children: lazy.loading ? "Carregando..." : lazy.error ? "Erro \u2014 tentar novamente" : bubble.viewVideo });
|
|
277
|
-
}
|
|
278
|
-
return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5("video", { src, className: "w-full max-h-80 rounded-lg", controls: true, preload: "metadata", children: /* @__PURE__ */ jsx5("track", { kind: "captions" }) }) : /* @__PURE__ */ jsx5("div", { className: "w-full h-32 bg-gray-200 rounded-lg flex items-center justify-center text-gray-400", children: /* @__PURE__ */ jsxs3("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
|
|
279
|
-
/* @__PURE__ */ jsx5("polygon", { points: "23 7 16 12 23 17 23 7" }),
|
|
280
|
-
/* @__PURE__ */ jsx5("rect", { x: "1", y: "5", width: "15", height: "14", rx: "2", ry: "2" })
|
|
281
|
-
] }) }) });
|
|
282
|
-
}
|
|
283
|
-
case "audio": {
|
|
284
|
-
if (!src && canLazyLoad) {
|
|
285
|
-
return /* @__PURE__ */ jsx5("button", { onClick: lazy.load, className: lazyButtonClass, children: lazy.loading ? "Carregando..." : lazy.error ? "Erro \u2014 tentar novamente" : bubble.listenAudio });
|
|
286
|
-
}
|
|
287
|
-
return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5(AudioPlayer, { src, isMine: message.direction === "outbound" }) : /* @__PURE__ */ jsx5("div", { className: "h-12 bg-gray-200 rounded-lg flex items-center justify-center text-gray-400 text-xs", children: "Audio unavailable" }) });
|
|
288
|
-
}
|
|
289
|
-
case "document": {
|
|
290
|
-
const typeLabel = message.mimeType?.split("/")[1]?.toUpperCase() ?? "FILE";
|
|
291
|
-
const sizeLabel = message.sizeBytes ? formatFileSize(message.sizeBytes) : null;
|
|
292
|
-
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 min-w-[200px]", children: [
|
|
293
|
-
/* @__PURE__ */ jsx5("div", { className: "w-10 h-10 bg-gray-200 rounded-lg flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx5(FileIcon, { filename: message.filename, mimeType: message.mimeType }) }),
|
|
294
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
|
|
295
|
-
/* @__PURE__ */ jsx5("p", { className: "text-sm font-medium truncate", children: message.filename ?? "Document" }),
|
|
296
|
-
/* @__PURE__ */ jsx5("p", { className: "text-xs text-gray-500", children: sizeLabel ? `${typeLabel} \xB7 ${sizeLabel}` : typeLabel })
|
|
297
|
-
] }),
|
|
298
|
-
src ? /* @__PURE__ */ jsx5("a", { href: src, download: message.filename, className: "w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 hover:bg-gray-300 flex-shrink-0 transition-colors", "aria-label": "Download", children: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600", children: [
|
|
299
|
-
/* @__PURE__ */ jsx5("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
300
|
-
/* @__PURE__ */ jsx5("polyline", { points: "7 10 12 15 17 10" }),
|
|
301
|
-
/* @__PURE__ */ jsx5("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
|
|
302
|
-
] }) }) : canLazyLoad ? /* @__PURE__ */ jsx5(
|
|
303
|
-
"button",
|
|
304
|
-
{
|
|
305
|
-
onClick: lazy.load,
|
|
306
|
-
disabled: lazy.loading,
|
|
307
|
-
className: "w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 hover:bg-gray-300 flex-shrink-0 transition-colors disabled:opacity-50",
|
|
308
|
-
"aria-label": "Download",
|
|
309
|
-
children: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600", children: [
|
|
310
|
-
/* @__PURE__ */ jsx5("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
311
|
-
/* @__PURE__ */ jsx5("polyline", { points: "7 10 12 15 17 10" }),
|
|
312
|
-
/* @__PURE__ */ jsx5("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
|
|
313
|
-
] })
|
|
314
|
-
}
|
|
315
|
-
) : null,
|
|
316
|
-
lazy.error && /* @__PURE__ */ jsx5("span", { className: "text-xs text-red-500 flex-shrink-0", children: "Erro" })
|
|
317
|
-
] });
|
|
318
|
-
}
|
|
319
|
-
default:
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// src/Lightbox.tsx
|
|
325
|
-
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
326
|
-
function Lightbox({ imageUrl, caption, onClose }) {
|
|
327
|
-
return /* @__PURE__ */ jsx6("div", { className: "fixed inset-0 z-50 bg-black/85 flex items-center justify-center p-4", onClick: onClose, children: /* @__PURE__ */ jsxs4("div", { className: "max-w-[90vw] max-h-[90vh] flex flex-col items-center", onClick: (e) => e.stopPropagation(), children: [
|
|
328
|
-
/* @__PURE__ */ jsx6("img", { src: imageUrl, alt: caption ?? "Image", className: "max-w-full max-h-[80vh] object-contain rounded-lg" }),
|
|
329
|
-
caption && /* @__PURE__ */ jsx6("p", { className: "text-white text-sm mt-3 text-center", children: caption }),
|
|
330
|
-
/* @__PURE__ */ jsx6("button", { onClick: onClose, className: "mt-4 px-4 py-2 bg-white/20 text-white rounded-lg hover:bg-white/30 transition-colors", children: "Fechar" })
|
|
331
|
-
] }) });
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// src/MessageBubble.tsx
|
|
335
|
-
import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
336
|
-
var BUBBLE_COLOR = {
|
|
337
|
-
agent: "bg-[#d9fdd3] dark:bg-[#005c4b]",
|
|
338
|
-
bot: "bg-[#d7f0ec] dark:bg-[#0a3d3a]",
|
|
339
|
-
customer: "bg-white dark:bg-[#202c33]"
|
|
340
|
-
};
|
|
341
|
-
var MEDIA_TYPES = /* @__PURE__ */ new Set(["image", "audio", "video", "document", "sticker"]);
|
|
342
|
-
function MessageBubble({
|
|
343
|
-
message,
|
|
344
|
-
isMine,
|
|
345
|
-
senderName,
|
|
346
|
-
isFirstInGroup = true,
|
|
347
|
-
isSelecting = false,
|
|
348
|
-
isSelected = false,
|
|
349
|
-
onToggleSelect,
|
|
350
|
-
onResolveMediaUrl
|
|
351
|
-
}) {
|
|
352
|
-
const { bubble, selection } = useConversationLocales();
|
|
353
|
-
const [lightboxSrc, setLightboxSrc] = useState3(null);
|
|
354
|
-
const bubbleColor = BUBBLE_COLOR[message.sender] ?? BUBBLE_COLOR.customer;
|
|
355
|
-
const hasError = message.status === "failed";
|
|
356
|
-
const isMedia = MEDIA_TYPES.has(message.type);
|
|
357
|
-
const isTemplate = message.type === "template";
|
|
358
|
-
const displayName = message.sender === "agent" && senderName ? senderName : bubble[message.sender] ?? message.sender;
|
|
359
|
-
const tooltipText = message.status === "read" && message.readAt ? `${bubble.readAt}${formatDateTime(message.readAt)}` : message.status === "failed" ? bubble.windowExpired : void 0;
|
|
360
|
-
const tailCornerClass = isFirstInGroup ? isMine ? "rounded-tr-md" : "rounded-tl-md" : "";
|
|
361
|
-
const checkbox = /* @__PURE__ */ jsx7(
|
|
362
|
-
"button",
|
|
363
|
-
{
|
|
364
|
-
onClick: (e) => {
|
|
365
|
-
e.stopPropagation();
|
|
366
|
-
onToggleSelect?.();
|
|
367
|
-
},
|
|
368
|
-
title: selection.select,
|
|
369
|
-
className: `
|
|
370
|
-
flex-shrink-0 self-end mb-1 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all
|
|
371
|
-
${isSelected ? "bg-teal-600 border-teal-600" : "bg-white/80 dark:bg-black/40 border-black/20 dark:border-white/30"}
|
|
372
|
-
${isSelecting ? "opacity-100" : "opacity-0 group-hover:opacity-100"}
|
|
373
|
-
`,
|
|
374
|
-
children: isSelected && /* @__PURE__ */ jsx7(Check, { size: 12, className: "text-white", strokeWidth: 3 })
|
|
375
|
-
}
|
|
376
|
-
);
|
|
377
|
-
return /* @__PURE__ */ jsxs5("div", { className: `flex items-end gap-1.5 ${isMine ? "justify-end" : "justify-start"} group ${isFirstInGroup ? "mt-2" : "mt-0.5"}`, children: [
|
|
378
|
-
isMine && checkbox,
|
|
379
|
-
/* @__PURE__ */ jsxs5(
|
|
380
|
-
"div",
|
|
381
|
-
{
|
|
382
|
-
onClick: isSelecting ? onToggleSelect : void 0,
|
|
383
|
-
className: `
|
|
384
|
-
max-w-[75%] sm:max-w-[65%] rounded-2xl ${tailCornerClass} px-2.5 py-1.5 shadow-sm
|
|
385
|
-
${bubbleColor}
|
|
386
|
-
${hasError ? "ring-1 ring-inset ring-red-400" : ""}
|
|
387
|
-
${isSelecting ? "cursor-pointer" : ""}
|
|
388
|
-
${isSelected ? "ring-2 ring-teal-500" : ""}
|
|
389
|
-
relative
|
|
390
|
-
`,
|
|
391
|
-
children: [
|
|
392
|
-
isMine && isFirstInGroup && (message.sender === "bot" || message.sender === "agent" && senderName) && /* @__PURE__ */ jsx7("div", { className: "text-xs font-semibold mb-0.5 text-teal-700 dark:text-teal-400", children: displayName }),
|
|
393
|
-
isMedia ? /* @__PURE__ */ jsx7(MediaRenderer, { message, onLightbox: setLightboxSrc, onResolveUrl: onResolveMediaUrl }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
394
|
-
isTemplate && /* @__PURE__ */ jsxs5("p", { className: "text-xs text-gray-500 dark:text-gray-400 flex items-center gap-1 mb-0.5", children: [
|
|
395
|
-
/* @__PURE__ */ jsx7("span", { children: "\u{1F4E8}" }),
|
|
396
|
-
/* @__PURE__ */ jsxs5("span", { className: "font-medium", children: [
|
|
397
|
-
bubble.templateLabel,
|
|
398
|
-
message.templateName ? ` \u2014 ${message.templateName}` : ""
|
|
399
|
-
] })
|
|
400
|
-
] }),
|
|
401
|
-
/* @__PURE__ */ jsx7("div", { className: "text-sm text-gray-900 dark:text-gray-100 whitespace-pre-wrap break-words leading-[19px]", children: parseWhatsAppFormatting(message.content ?? "") })
|
|
402
|
-
] }),
|
|
403
|
-
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-end gap-1 mt-0.5 select-none", children: [
|
|
404
|
-
/* @__PURE__ */ jsx7("span", { className: "text-xs text-black/40 dark:text-white/40 font-medium", children: formatTimestamp(message.timestamp) }),
|
|
405
|
-
isMine && message.status && /* @__PURE__ */ jsx7(StatusTicks, { status: message.status, title: tooltipText })
|
|
406
|
-
] })
|
|
407
|
-
]
|
|
408
|
-
}
|
|
409
|
-
),
|
|
410
|
-
!isMine && checkbox,
|
|
411
|
-
lightboxSrc && /* @__PURE__ */ jsx7(Lightbox, { imageUrl: lightboxSrc, onClose: () => setLightboxSrc(null) })
|
|
412
|
-
] });
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// src/Wallpaper.tsx
|
|
416
|
-
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
417
|
-
function ConversationWallpaper({ children, className = "" }) {
|
|
418
|
-
return /* @__PURE__ */ jsx8("div", { className: `cv-wallpaper ${className}`.trim(), children });
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// src/EmojiPicker.tsx
|
|
422
|
-
import { useState as useState4, useCallback } from "react";
|
|
423
|
-
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
424
|
-
var EMOJI_CATEGORIES = [
|
|
425
|
-
{
|
|
426
|
-
name: "Smileys",
|
|
427
|
-
emojis: ["\u{1F600}", "\u{1F603}", "\u{1F604}", "\u{1F601}", "\u{1F605}", "\u{1F602}", "\u{1F923}", "\u{1F60A}", "\u{1F607}", "\u{1F642}", "\u{1F609}", "\u{1F60C}", "\u{1F60D}", "\u{1F970}", "\u{1F618}", "\u{1F617}", "\u{1F60B}", "\u{1F61B}", "\u{1F61C}", "\u{1F92A}"]
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
name: "Gestures",
|
|
431
|
-
emojis: ["\u{1F44D}", "\u{1F44E}", "\u{1F44C}", "\u270C\uFE0F", "\u{1F91E}", "\u{1F91F}", "\u{1F918}", "\u{1F919}", "\u{1F44B}", "\u{1F91A}", "\u{1F590}\uFE0F", "\u270B", "\u{1F596}", "\u{1F44F}", "\u{1F64C}", "\u{1F91D}", "\u{1F64F}", "\u270D\uFE0F", "\u{1F485}", "\u{1F933}"]
|
|
432
|
-
},
|
|
433
|
-
{
|
|
434
|
-
name: "Hearts",
|
|
435
|
-
emojis: ["\u2764\uFE0F", "\u{1F9E1}", "\u{1F49B}", "\u{1F49A}", "\u{1F499}", "\u{1F49C}", "\u{1F5A4}", "\u{1F90D}", "\u{1F90E}", "\u{1F494}", "\u2763\uFE0F", "\u{1F495}", "\u{1F49E}", "\u{1F493}", "\u{1F497}", "\u{1F496}", "\u{1F498}", "\u{1F49D}", "\u{1F49F}", "\u2665\uFE0F"]
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
name: "Food",
|
|
439
|
-
emojis: ["\u{1F354}", "\u{1F35F}", "\u{1F355}", "\u{1F32D}", "\u{1F37F}", "\u{1F9C2}", "\u{1F953}", "\u{1F95A}", "\u{1F373}", "\u{1F9C7}", "\u{1F95E}", "\u{1F9C8}", "\u{1F35E}", "\u{1F950}", "\u{1F968}", "\u{1F96F}", "\u{1F956}", "\u{1F9C0}", "\u{1F957}", "\u{1F959}"]
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
name: "Drinks",
|
|
443
|
-
emojis: ["\u2615", "\u{1F375}", "\u{1F376}", "\u{1F37E}", "\u{1F377}", "\u{1F378}", "\u{1F379}", "\u{1F37A}", "\u{1F37B}", "\u{1F942}", "\u{1F943}", "\u{1F964}", "\u{1F9CB}", "\u{1F9C3}", "\u{1F9C9}", "\u{1F9CA}", "\u{1F962}", "\u{1F37D}\uFE0F", "\u{1F374}", "\u{1F944}"]
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
name: "Objects",
|
|
447
|
-
emojis: ["\u{1F381}", "\u{1F382}", "\u{1F388}", "\u{1F389}", "\u{1F38A}", "\u{1F380}", "\u{1F4F1}", "\u{1F4BB}", "\u231A", "\u{1F4F7}", "\u{1F511}", "\u{1F4B0}", "\u{1F4B3}", "\u{1F4DD}", "\u{1F4CC}", "\u{1F4CD}", "\u2702\uFE0F", "\u{1F50D}", "\u{1F4A1}", "\u{1F514}"]
|
|
448
|
-
}
|
|
449
|
-
];
|
|
450
|
-
var EmojiPicker = ({ onSelect, className = "" }) => {
|
|
451
|
-
const [activeCategory, setActiveCategory] = useState4(0);
|
|
452
|
-
const handleSelect = useCallback(
|
|
453
|
-
(emoji) => {
|
|
454
|
-
onSelect(emoji);
|
|
455
|
-
},
|
|
456
|
-
[onSelect]
|
|
457
|
-
);
|
|
458
|
-
return /* @__PURE__ */ jsxs6("div", { className: `bg-white border border-gray-200 rounded-lg shadow-lg overflow-hidden ${className}`, children: [
|
|
459
|
-
/* @__PURE__ */ jsx9("div", { className: "flex border-b border-gray-200 overflow-x-auto", children: EMOJI_CATEGORIES.map((category, index) => /* @__PURE__ */ jsxs6(
|
|
460
|
-
"button",
|
|
461
|
-
{
|
|
462
|
-
onClick: () => setActiveCategory(index),
|
|
463
|
-
className: `px-3 py-2 text-xs font-medium whitespace-nowrap border-b-2 transition-colors ${activeCategory === index ? "border-blue-500 text-blue-600" : "border-transparent text-gray-500 hover:text-gray-700"}`,
|
|
464
|
-
children: [
|
|
465
|
-
category.emojis[0],
|
|
466
|
-
" ",
|
|
467
|
-
category.name
|
|
468
|
-
]
|
|
469
|
-
},
|
|
470
|
-
category.name
|
|
471
|
-
)) }),
|
|
472
|
-
/* @__PURE__ */ jsx9("div", { className: "grid grid-cols-10 gap-0.5 p-2 max-h-[240px] overflow-y-auto", children: EMOJI_CATEGORIES[activeCategory].emojis.map((emoji) => /* @__PURE__ */ jsx9(
|
|
473
|
-
"button",
|
|
474
|
-
{
|
|
475
|
-
onClick: () => handleSelect(emoji),
|
|
476
|
-
className: "w-8 h-8 flex items-center justify-center text-lg hover:bg-gray-100 rounded transition-colors cursor-pointer",
|
|
477
|
-
"aria-label": emoji,
|
|
478
|
-
children: emoji
|
|
479
|
-
},
|
|
480
|
-
emoji
|
|
481
|
-
)) })
|
|
482
|
-
] });
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
// src/MessageComposer.tsx
|
|
486
|
-
import { useState as useState5, useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
487
|
-
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
488
|
-
var DEFAULT_ACCEPTED_FILE_TYPES = "image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx,.zip";
|
|
489
|
-
var MessageComposer = ({
|
|
490
|
-
onSend,
|
|
491
|
-
onAttach,
|
|
492
|
-
value: externalValue,
|
|
493
|
-
onChange: externalOnChange,
|
|
494
|
-
features,
|
|
495
|
-
placeholder = "Digite uma mensagem...",
|
|
496
|
-
maxLength,
|
|
497
|
-
disabled = false,
|
|
498
|
-
acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES
|
|
499
|
-
}) => {
|
|
500
|
-
const [internalText, setInternalText] = useState5("");
|
|
501
|
-
const [showEmoji, setShowEmoji] = useState5(false);
|
|
502
|
-
const [attachments, setAttachments] = useState5([]);
|
|
503
|
-
const textareaRef = useRef2(null);
|
|
504
|
-
const fileInputRef = useRef2(null);
|
|
505
|
-
const isControlled = externalValue !== void 0;
|
|
506
|
-
const text = isControlled ? externalValue : internalText;
|
|
507
|
-
const setText = useCallback2((newText) => {
|
|
508
|
-
if (isControlled) {
|
|
509
|
-
externalOnChange?.(newText);
|
|
510
|
-
} else {
|
|
511
|
-
setInternalText(newText);
|
|
512
|
-
}
|
|
513
|
-
}, [isControlled, externalOnChange]);
|
|
514
|
-
const showEmojiButton = features?.emoji !== false;
|
|
515
|
-
const showAttachButton = features?.documents !== false;
|
|
516
|
-
const sendMessage = useCallback2(() => {
|
|
517
|
-
const trimmed = text.trim();
|
|
518
|
-
if (!trimmed && attachments.length === 0) return;
|
|
519
|
-
if (trimmed) onSend(trimmed);
|
|
520
|
-
for (const a of attachments) {
|
|
521
|
-
onAttach?.(a.file);
|
|
522
|
-
URL.revokeObjectURL(a.previewUrl);
|
|
523
|
-
}
|
|
524
|
-
if (!isControlled) setInternalText("");
|
|
525
|
-
setAttachments([]);
|
|
526
|
-
setShowEmoji(false);
|
|
527
|
-
if (textareaRef.current) textareaRef.current.style.height = "auto";
|
|
528
|
-
}, [text, attachments, onSend, onAttach, isControlled]);
|
|
529
|
-
const handleKeyDown = useCallback2((e) => {
|
|
530
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
531
|
-
e.preventDefault();
|
|
532
|
-
if (!disabled) sendMessage();
|
|
533
|
-
}
|
|
534
|
-
}, [sendMessage, disabled]);
|
|
535
|
-
const handleInput = useCallback2(() => {
|
|
536
|
-
const ta = textareaRef.current;
|
|
537
|
-
if (!ta) return;
|
|
538
|
-
ta.style.height = "auto";
|
|
539
|
-
ta.style.height = `${Math.min(ta.scrollHeight, 100)}px`;
|
|
540
|
-
}, []);
|
|
541
|
-
const handleEmojiSelect = useCallback2((emoji) => {
|
|
542
|
-
const ta = textareaRef.current;
|
|
543
|
-
if (!ta) {
|
|
544
|
-
setText(text + emoji);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
const start = ta.selectionStart;
|
|
548
|
-
const end = ta.selectionEnd;
|
|
549
|
-
const newText = text.slice(0, start) + emoji + text.slice(end);
|
|
550
|
-
setText(newText);
|
|
551
|
-
requestAnimationFrame(() => {
|
|
552
|
-
ta.focus();
|
|
553
|
-
ta.setSelectionRange(start + emoji.length, start + emoji.length);
|
|
554
|
-
handleInput();
|
|
555
|
-
});
|
|
556
|
-
}, [text, setText, handleInput]);
|
|
557
|
-
const handleFileChange = useCallback2((e) => {
|
|
558
|
-
const files = e.target.files;
|
|
559
|
-
if (!files) return;
|
|
560
|
-
const previews = [];
|
|
561
|
-
for (let i = 0; i < files.length; i++) {
|
|
562
|
-
const file = files[i];
|
|
563
|
-
previews.push({ file, previewUrl: file.type.startsWith("image/") ? URL.createObjectURL(file) : "" });
|
|
564
|
-
}
|
|
565
|
-
setAttachments((prev) => [...prev, ...previews]);
|
|
566
|
-
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
567
|
-
}, []);
|
|
568
|
-
const removeAttachment = useCallback2((index) => {
|
|
569
|
-
setAttachments((prev) => {
|
|
570
|
-
const next = [...prev];
|
|
571
|
-
if (next[index].previewUrl) URL.revokeObjectURL(next[index].previewUrl);
|
|
572
|
-
next.splice(index, 1);
|
|
573
|
-
return next;
|
|
574
|
-
});
|
|
575
|
-
}, []);
|
|
576
|
-
const insertFormatting = useCallback2((marker) => {
|
|
577
|
-
const ta = textareaRef.current;
|
|
578
|
-
if (!ta) return;
|
|
579
|
-
const start = ta.selectionStart;
|
|
580
|
-
const end = ta.selectionEnd;
|
|
581
|
-
const sel = text.slice(start, end);
|
|
582
|
-
if (sel) {
|
|
583
|
-
setText(text.slice(0, start) + marker + sel + marker + text.slice(end));
|
|
584
|
-
requestAnimationFrame(() => {
|
|
585
|
-
ta.focus();
|
|
586
|
-
ta.setSelectionRange(start + marker.length + sel.length + marker.length, start + marker.length + sel.length + marker.length);
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
}, [text, setText]);
|
|
590
|
-
const canSend = text.trim().length > 0 || attachments.length > 0;
|
|
591
|
-
const remaining = maxLength ? maxLength - text.length : null;
|
|
592
|
-
return /* @__PURE__ */ jsxs7("div", { children: [
|
|
593
|
-
attachments.length > 0 && /* @__PURE__ */ jsx10("div", { className: "flex gap-2 px-1 pb-2 overflow-x-auto", children: attachments.map((a, i) => /* @__PURE__ */ jsxs7("div", { className: "relative flex-shrink-0", children: [
|
|
594
|
-
a.previewUrl ? /* @__PURE__ */ jsx10("img", { src: a.previewUrl, alt: "", className: "w-16 h-16 object-cover rounded-lg border border-gray-200" }) : /* @__PURE__ */ jsx10("div", { className: "w-16 h-16 bg-gray-100 rounded-lg border border-gray-200 flex items-center justify-center", children: /* @__PURE__ */ jsxs7("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#9ca3af", strokeWidth: "1.5", children: [
|
|
595
|
-
/* @__PURE__ */ jsx10("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
596
|
-
/* @__PURE__ */ jsx10("polyline", { points: "14 2 14 8 20 8" })
|
|
597
|
-
] }) }),
|
|
598
|
-
/* @__PURE__ */ jsx10("button", { onClick: () => removeAttachment(i), className: "absolute -top-2 -right-2 w-5 h-5 bg-gray-600 text-white rounded-full flex items-center justify-center hover:bg-gray-800 text-xs", children: "\u2715" })
|
|
599
|
-
] }, i)) }),
|
|
600
|
-
/* @__PURE__ */ jsxs7("div", { className: "flex items-end gap-1.5 bg-[#f0f2f5] rounded-xl px-3 py-2", children: [
|
|
601
|
-
showEmojiButton && /* @__PURE__ */ jsxs7("div", { className: "relative flex-shrink-0", children: [
|
|
602
|
-
/* @__PURE__ */ jsx10("button", { onClick: () => setShowEmoji((v) => !v), className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 transition-colors", "aria-label": "Emoji", children: /* @__PURE__ */ jsxs7("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
603
|
-
/* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "10" }),
|
|
604
|
-
/* @__PURE__ */ jsx10("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
605
|
-
/* @__PURE__ */ jsx10("circle", { cx: "9", cy: "9", r: "0.5", fill: "currentColor" }),
|
|
606
|
-
/* @__PURE__ */ jsx10("circle", { cx: "15", cy: "9", r: "0.5", fill: "currentColor" })
|
|
607
|
-
] }) }),
|
|
608
|
-
showEmoji && /* @__PURE__ */ jsx10("div", { className: "absolute bottom-full left-0 mb-2 z-10", children: /* @__PURE__ */ jsx10(EmojiPicker, { onSelect: handleEmojiSelect }) })
|
|
609
|
-
] }),
|
|
610
|
-
/* @__PURE__ */ jsx10(
|
|
611
|
-
"textarea",
|
|
612
|
-
{
|
|
613
|
-
ref: textareaRef,
|
|
614
|
-
value: text,
|
|
615
|
-
onChange: (e) => {
|
|
616
|
-
setText(e.target.value);
|
|
617
|
-
handleInput();
|
|
618
|
-
},
|
|
619
|
-
onKeyDown: handleKeyDown,
|
|
620
|
-
placeholder,
|
|
621
|
-
rows: 1,
|
|
622
|
-
disabled,
|
|
623
|
-
className: "flex-1 resize-none bg-transparent text-[15px] text-[#3b4a54] placeholder-[#8696a0] outline-none py-1.5 max-h-[100px] leading-relaxed",
|
|
624
|
-
style: { fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" }
|
|
625
|
-
}
|
|
626
|
-
),
|
|
627
|
-
showAttachButton && /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
628
|
-
/* @__PURE__ */ jsx10("input", { ref: fileInputRef, type: "file", multiple: true, accept: acceptedFileTypes, onChange: handleFileChange, className: "hidden" }),
|
|
629
|
-
/* @__PURE__ */ jsx10("button", { onClick: () => fileInputRef.current?.click(), className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 flex-shrink-0 transition-colors", "aria-label": "Anexar", children: /* @__PURE__ */ jsx10("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx10("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }) })
|
|
630
|
-
] }),
|
|
631
|
-
/* @__PURE__ */ jsx10(
|
|
632
|
-
"button",
|
|
633
|
-
{
|
|
634
|
-
onClick: sendMessage,
|
|
635
|
-
disabled: !canSend || disabled,
|
|
636
|
-
className: `w-10 h-10 flex items-center justify-center rounded-full flex-shrink-0 transition-all ${canSend && !disabled ? "bg-[#00a884] text-white hover:bg-[#06cf9c] shadow-sm" : "bg-gray-200 text-gray-400 cursor-not-allowed"}`,
|
|
637
|
-
"aria-label": "Enviar",
|
|
638
|
-
children: /* @__PURE__ */ jsx10("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx10("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
|
|
639
|
-
}
|
|
640
|
-
)
|
|
641
|
-
] }),
|
|
642
|
-
remaining !== null && /* @__PURE__ */ jsx10("div", { className: "flex justify-end mt-1 pr-1", children: /* @__PURE__ */ jsx10("span", { className: `text-xs ${remaining < 20 ? "text-red-500" : "text-gray-400"}`, children: remaining }) })
|
|
643
|
-
] });
|
|
644
|
-
};
|
|
27
|
+
} from "./chunk-OGRRHQQW.js";
|
|
645
28
|
|
|
646
29
|
// src/WhatsAppMessageEditor.tsx
|
|
647
|
-
import { useRef
|
|
30
|
+
import { useRef } from "react";
|
|
648
31
|
import { Bold, Italic, Strikethrough } from "lucide-react";
|
|
649
|
-
import { Fragment
|
|
32
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
650
33
|
var INLINE_TOKEN = /(\*[^*\n]+\*|_[^_\n]+_|~[^~\n]+~|`[^`\n]+`|\{[a-zA-Z_]+\})/g;
|
|
651
34
|
function renderLine(line, lineKey) {
|
|
652
35
|
const nodes = [];
|
|
@@ -658,11 +41,11 @@ function renderLine(line, lineKey) {
|
|
|
658
41
|
if (match.index > lastIndex) nodes.push(line.slice(lastIndex, match.index));
|
|
659
42
|
const token = match[0];
|
|
660
43
|
const key = `${lineKey}-${tokenIndex++}`;
|
|
661
|
-
if (token.startsWith("*")) nodes.push(/* @__PURE__ */
|
|
662
|
-
else if (token.startsWith("_")) nodes.push(/* @__PURE__ */
|
|
663
|
-
else if (token.startsWith("~")) nodes.push(/* @__PURE__ */
|
|
664
|
-
else if (token.startsWith("`")) nodes.push(/* @__PURE__ */
|
|
665
|
-
else nodes.push(/* @__PURE__ */
|
|
44
|
+
if (token.startsWith("*")) nodes.push(/* @__PURE__ */ jsx("strong", { children: token.slice(1, -1) }, key));
|
|
45
|
+
else if (token.startsWith("_")) nodes.push(/* @__PURE__ */ jsx("em", { children: token.slice(1, -1) }, key));
|
|
46
|
+
else if (token.startsWith("~")) nodes.push(/* @__PURE__ */ jsx("s", { children: token.slice(1, -1) }, key));
|
|
47
|
+
else if (token.startsWith("`")) nodes.push(/* @__PURE__ */ jsx("code", { className: "font-mono text-[0.85em]", children: token.slice(1, -1) }, key));
|
|
48
|
+
else nodes.push(/* @__PURE__ */ jsx("span", { className: "rounded bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 px-1", children: token }, key));
|
|
666
49
|
lastIndex = INLINE_TOKEN.lastIndex;
|
|
667
50
|
}
|
|
668
51
|
if (lastIndex < line.length) nodes.push(line.slice(lastIndex));
|
|
@@ -670,9 +53,9 @@ function renderLine(line, lineKey) {
|
|
|
670
53
|
}
|
|
671
54
|
function renderWhatsApp(text) {
|
|
672
55
|
const lines = text.split("\n");
|
|
673
|
-
return lines.map((line, index) => /* @__PURE__ */
|
|
56
|
+
return lines.map((line, index) => /* @__PURE__ */ jsxs("span", { children: [
|
|
674
57
|
renderLine(line, `ln-${index}`),
|
|
675
|
-
index < lines.length - 1 ? /* @__PURE__ */
|
|
58
|
+
index < lines.length - 1 ? /* @__PURE__ */ jsx("br", {}) : null
|
|
676
59
|
] }, `ln-${index}`));
|
|
677
60
|
}
|
|
678
61
|
function WhatsAppMessageEditor({
|
|
@@ -684,7 +67,7 @@ function WhatsAppMessageEditor({
|
|
|
684
67
|
previewLabel = "Pr\xE9via (como aparece no WhatsApp)",
|
|
685
68
|
emptyPreviewText = "Sua mensagem aparecer\xE1 aqui\u2026"
|
|
686
69
|
}) {
|
|
687
|
-
const textareaRef =
|
|
70
|
+
const textareaRef = useRef(null);
|
|
688
71
|
function wrapSelection(marker) {
|
|
689
72
|
const textarea = textareaRef.current;
|
|
690
73
|
if (!textarea) return;
|
|
@@ -712,14 +95,14 @@ function WhatsAppMessageEditor({
|
|
|
712
95
|
});
|
|
713
96
|
}
|
|
714
97
|
const toolbarButtonClass = "inline-flex items-center justify-center h-8 w-8 rounded-lg border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors";
|
|
715
|
-
return /* @__PURE__ */
|
|
716
|
-
/* @__PURE__ */
|
|
717
|
-
/* @__PURE__ */
|
|
718
|
-
/* @__PURE__ */
|
|
719
|
-
/* @__PURE__ */
|
|
720
|
-
placeholders.length > 0 && /* @__PURE__ */
|
|
721
|
-
/* @__PURE__ */
|
|
722
|
-
placeholders.map((token) => /* @__PURE__ */
|
|
98
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
99
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5 mb-2", children: [
|
|
100
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => wrapSelection("*"), className: toolbarButtonClass, title: "Negrito (*texto*)", "aria-label": "Negrito", children: /* @__PURE__ */ jsx(Bold, { size: 15 }) }),
|
|
101
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => wrapSelection("_"), className: toolbarButtonClass, title: "It\xE1lico (_texto_)", "aria-label": "It\xE1lico", children: /* @__PURE__ */ jsx(Italic, { size: 15 }) }),
|
|
102
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => wrapSelection("~"), className: toolbarButtonClass, title: "Tachado (~texto~)", "aria-label": "Tachado", children: /* @__PURE__ */ jsx(Strikethrough, { size: 15 }) }),
|
|
103
|
+
placeholders.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
104
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1 h-5 w-px bg-gray-200 dark:bg-gray-600" }),
|
|
105
|
+
placeholders.map((token) => /* @__PURE__ */ jsx(
|
|
723
106
|
"button",
|
|
724
107
|
{
|
|
725
108
|
type: "button",
|
|
@@ -732,7 +115,7 @@ function WhatsAppMessageEditor({
|
|
|
732
115
|
))
|
|
733
116
|
] })
|
|
734
117
|
] }),
|
|
735
|
-
/* @__PURE__ */
|
|
118
|
+
/* @__PURE__ */ jsx(
|
|
736
119
|
"textarea",
|
|
737
120
|
{
|
|
738
121
|
ref: textareaRef,
|
|
@@ -743,17 +126,17 @@ function WhatsAppMessageEditor({
|
|
|
743
126
|
className: "w-full border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all placeholder:text-gray-400 dark:placeholder:text-gray-500 resize-y"
|
|
744
127
|
}
|
|
745
128
|
),
|
|
746
|
-
/* @__PURE__ */
|
|
747
|
-
/* @__PURE__ */
|
|
748
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
|
|
130
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mb-1", children: previewLabel }),
|
|
131
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-xl bg-[#e5ded8] dark:bg-gray-900 p-3", children: /* @__PURE__ */ jsx("div", { className: "inline-block max-w-full rounded-lg rounded-tl-none bg-white dark:bg-gray-800 shadow-sm px-3 py-2 text-sm text-gray-800 dark:text-gray-100 whitespace-pre-wrap break-words", children: value.trim() ? renderWhatsApp(value) : /* @__PURE__ */ jsx("span", { className: "text-gray-400 dark:text-gray-500", children: emptyPreviewText }) }) })
|
|
749
132
|
] })
|
|
750
133
|
] });
|
|
751
134
|
}
|
|
752
135
|
|
|
753
136
|
// src/SimpleEmojiPicker.tsx
|
|
754
|
-
import { useEffect
|
|
137
|
+
import { useEffect, useRef as useRef2, useState } from "react";
|
|
755
138
|
import { Smile } from "lucide-react";
|
|
756
|
-
import { jsx as
|
|
139
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
757
140
|
var EMOJIS = [
|
|
758
141
|
"\u{1F600}",
|
|
759
142
|
"\u{1F602}",
|
|
@@ -837,9 +220,9 @@ var EMOJIS = [
|
|
|
837
220
|
"\u{1F6E1}\uFE0F"
|
|
838
221
|
];
|
|
839
222
|
function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px", pickerMaxHeight = "220px" }) {
|
|
840
|
-
const [open, setOpen] =
|
|
841
|
-
const containerRef =
|
|
842
|
-
|
|
223
|
+
const [open, setOpen] = useState(false);
|
|
224
|
+
const containerRef = useRef2(null);
|
|
225
|
+
useEffect(() => {
|
|
843
226
|
if (!open) return;
|
|
844
227
|
const handleClickOutside = (e) => {
|
|
845
228
|
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
@@ -849,23 +232,23 @@ function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px",
|
|
|
849
232
|
document.addEventListener("mousedown", handleClickOutside);
|
|
850
233
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
851
234
|
}, [open]);
|
|
852
|
-
return /* @__PURE__ */
|
|
853
|
-
/* @__PURE__ */
|
|
235
|
+
return /* @__PURE__ */ jsxs2("div", { ref: containerRef, className: "relative flex-shrink-0", children: [
|
|
236
|
+
/* @__PURE__ */ jsx2(
|
|
854
237
|
"button",
|
|
855
238
|
{
|
|
856
239
|
type: "button",
|
|
857
240
|
onClick: () => setOpen((v) => !v),
|
|
858
241
|
className: "w-7 h-7 flex items-center justify-center rounded-lg text-gray-400 dark:text-gray-500 hover:text-teal-600 dark:hover:text-teal-400 hover:bg-teal-50 dark:hover:bg-teal-900/30 transition-colors",
|
|
859
242
|
title: label,
|
|
860
|
-
children: /* @__PURE__ */
|
|
243
|
+
children: /* @__PURE__ */ jsx2(Smile, { size: 15 })
|
|
861
244
|
}
|
|
862
245
|
),
|
|
863
|
-
open && /* @__PURE__ */
|
|
246
|
+
open && /* @__PURE__ */ jsx2("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-xl shadow-xl p-2 z-50", children: /* @__PURE__ */ jsx2(
|
|
864
247
|
"div",
|
|
865
248
|
{
|
|
866
249
|
className: "grid grid-cols-10 gap-0.5 overflow-y-auto",
|
|
867
250
|
style: { width: pickerWidth, maxHeight: pickerMaxHeight },
|
|
868
|
-
children: EMOJIS.map((emoji) => /* @__PURE__ */
|
|
251
|
+
children: EMOJIS.map((emoji) => /* @__PURE__ */ jsx2(
|
|
869
252
|
"button",
|
|
870
253
|
{
|
|
871
254
|
type: "button",
|
|
@@ -883,24 +266,8 @@ function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px",
|
|
|
883
266
|
] });
|
|
884
267
|
}
|
|
885
268
|
|
|
886
|
-
// src/DateDivider.tsx
|
|
887
|
-
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
888
|
-
function DateDivider({ iso }) {
|
|
889
|
-
const { dateDivider } = useConversationLocales();
|
|
890
|
-
return /* @__PURE__ */ jsx13("div", { className: "flex justify-center sticky top-0 z-10 my-2 pointer-events-none", children: /* @__PURE__ */ jsx13("span", { className: "bg-white/95 dark:bg-gray-800/95 text-gray-600 dark:text-gray-300 text-xs font-medium px-3 py-1 rounded-lg shadow-sm", children: formatDividerLabel(iso, dateDivider) }) });
|
|
891
|
-
}
|
|
892
|
-
function formatDividerLabel(iso, dateDivider) {
|
|
893
|
-
const date = new Date(iso);
|
|
894
|
-
const today = /* @__PURE__ */ new Date();
|
|
895
|
-
const yesterday = new Date(today);
|
|
896
|
-
yesterday.setDate(today.getDate() - 1);
|
|
897
|
-
if (isSameDay(date, today)) return dateDivider.today;
|
|
898
|
-
if (isSameDay(date, yesterday)) return dateDivider.yesterday;
|
|
899
|
-
return date.toLocaleDateString("pt-BR", { day: "2-digit", month: "long", year: "numeric" });
|
|
900
|
-
}
|
|
901
|
-
|
|
902
269
|
// src/Avatar.tsx
|
|
903
|
-
import { jsx as
|
|
270
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
904
271
|
var sizeClasses = {
|
|
905
272
|
sm: "w-6 h-6 text-xs",
|
|
906
273
|
md: "w-8 h-8 text-sm",
|
|
@@ -929,7 +296,7 @@ function getBgColor(name) {
|
|
|
929
296
|
function Avatar({ name, avatarUrl, size = "md", className = "" }) {
|
|
930
297
|
const sizeClass = sizeClasses[size];
|
|
931
298
|
if (avatarUrl) {
|
|
932
|
-
return /* @__PURE__ */
|
|
299
|
+
return /* @__PURE__ */ jsx3(
|
|
933
300
|
"img",
|
|
934
301
|
{
|
|
935
302
|
src: avatarUrl,
|
|
@@ -938,14 +305,24 @@ function Avatar({ name, avatarUrl, size = "md", className = "" }) {
|
|
|
938
305
|
}
|
|
939
306
|
);
|
|
940
307
|
}
|
|
941
|
-
const initials = getInitials(name);
|
|
942
308
|
const bgColor = getBgColor(name);
|
|
943
|
-
|
|
309
|
+
if (!name) {
|
|
310
|
+
return /* @__PURE__ */ jsx3(
|
|
311
|
+
"div",
|
|
312
|
+
{
|
|
313
|
+
className: `${sizeClass} ${bgColor} rounded-full flex items-center justify-center text-white flex-shrink-0 ${className}`,
|
|
314
|
+
role: "img",
|
|
315
|
+
"aria-label": "Contato sem nome",
|
|
316
|
+
children: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", fill: "currentColor", className: "h-[60%] w-[60%]", "aria-hidden": true, children: /* @__PURE__ */ jsx3("path", { d: "M12 12a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0 2c-4.42 0-8 2.24-8 5v1h16v-1c0-2.76-3.58-5-8-5Z" }) })
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
return /* @__PURE__ */ jsx3(
|
|
944
321
|
"div",
|
|
945
322
|
{
|
|
946
323
|
className: `${sizeClass} ${bgColor} rounded-full flex items-center justify-center text-white font-semibold flex-shrink-0 ${className}`,
|
|
947
|
-
title: name
|
|
948
|
-
children:
|
|
324
|
+
title: name,
|
|
325
|
+
children: getInitials(name)
|
|
949
326
|
}
|
|
950
327
|
);
|
|
951
328
|
}
|
|
@@ -970,13 +347,115 @@ function formatPhone(number) {
|
|
|
970
347
|
}
|
|
971
348
|
return number;
|
|
972
349
|
}
|
|
350
|
+
var COUNTRY_FLAG_BY_DIAL_CODE = {
|
|
351
|
+
"55": "\u{1F1E7}\u{1F1F7}",
|
|
352
|
+
"351": "\u{1F1F5}\u{1F1F9}",
|
|
353
|
+
"34": "\u{1F1EA}\u{1F1F8}",
|
|
354
|
+
"54": "\u{1F1E6}\u{1F1F7}",
|
|
355
|
+
"56": "\u{1F1E8}\u{1F1F1}",
|
|
356
|
+
"57": "\u{1F1E8}\u{1F1F4}",
|
|
357
|
+
"52": "\u{1F1F2}\u{1F1FD}",
|
|
358
|
+
"598": "\u{1F1FA}\u{1F1FE}",
|
|
359
|
+
"595": "\u{1F1F5}\u{1F1FE}",
|
|
360
|
+
"44": "\u{1F1EC}\u{1F1E7}",
|
|
361
|
+
"49": "\u{1F1E9}\u{1F1EA}",
|
|
362
|
+
"39": "\u{1F1EE}\u{1F1F9}",
|
|
363
|
+
"33": "\u{1F1EB}\u{1F1F7}"
|
|
364
|
+
};
|
|
365
|
+
function phoneCountryFlag(number) {
|
|
366
|
+
const digits = number.replace(/\D/g, "");
|
|
367
|
+
for (const length of [3, 2, 1]) {
|
|
368
|
+
const flag = COUNTRY_FLAG_BY_DIAL_CODE[digits.slice(0, length)];
|
|
369
|
+
if (flag) return flag;
|
|
370
|
+
}
|
|
371
|
+
return "";
|
|
372
|
+
}
|
|
973
373
|
function phoneInitials(number) {
|
|
974
374
|
const digits = number.replace(/\D/g, "");
|
|
975
375
|
return digits.slice(-2);
|
|
976
376
|
}
|
|
977
377
|
|
|
378
|
+
// src/conversationChannel.ts
|
|
379
|
+
var CONVERSATION_CHANNEL = {
|
|
380
|
+
WHATSAPP: "whatsapp",
|
|
381
|
+
MESSENGER: "messenger",
|
|
382
|
+
INSTAGRAM: "instagram",
|
|
383
|
+
WEBCHAT: "webchat"
|
|
384
|
+
};
|
|
385
|
+
var DEFAULT_CONVERSATION_CHANNEL = CONVERSATION_CHANNEL.WHATSAPP;
|
|
386
|
+
var REOPEN_MECHANISM = {
|
|
387
|
+
TEMPLATE: "template",
|
|
388
|
+
TAG: "tag",
|
|
389
|
+
NONE: "none"
|
|
390
|
+
};
|
|
391
|
+
var HANDLE_KIND = {
|
|
392
|
+
PHONE: "phone",
|
|
393
|
+
USERNAME: "username",
|
|
394
|
+
SESSION: "session"
|
|
395
|
+
};
|
|
396
|
+
var CHANNEL_CAPABILITIES = {
|
|
397
|
+
[CONVERSATION_CHANNEL.WHATSAPP]: {
|
|
398
|
+
label: "WhatsApp",
|
|
399
|
+
icon: "\u{1F4AC}",
|
|
400
|
+
hasSessionWindow: true,
|
|
401
|
+
windowHours: 24,
|
|
402
|
+
reopenMechanism: REOPEN_MECHANISM.TEMPLATE,
|
|
403
|
+
handleKind: HANDLE_KIND.PHONE
|
|
404
|
+
},
|
|
405
|
+
[CONVERSATION_CHANNEL.MESSENGER]: {
|
|
406
|
+
// Messenger também tem 24h, mas reabre com message tag — não com template aprovado.
|
|
407
|
+
label: "Messenger",
|
|
408
|
+
icon: "\u{1F4E8}",
|
|
409
|
+
hasSessionWindow: true,
|
|
410
|
+
windowHours: 24,
|
|
411
|
+
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
412
|
+
handleKind: HANDLE_KIND.USERNAME
|
|
413
|
+
},
|
|
414
|
+
[CONVERSATION_CHANNEL.INSTAGRAM]: {
|
|
415
|
+
label: "Instagram",
|
|
416
|
+
icon: "\u{1F4F7}",
|
|
417
|
+
hasSessionWindow: true,
|
|
418
|
+
windowHours: 24,
|
|
419
|
+
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
420
|
+
handleKind: HANDLE_KIND.USERNAME
|
|
421
|
+
},
|
|
422
|
+
[CONVERSATION_CHANNEL.WEBCHAT]: {
|
|
423
|
+
// Chat próprio: sem intermediário, sem janela. Bloquear o composer aqui seria inventar limite.
|
|
424
|
+
label: "Chat do site",
|
|
425
|
+
icon: "\u{1F310}",
|
|
426
|
+
hasSessionWindow: false,
|
|
427
|
+
windowHours: 0,
|
|
428
|
+
reopenMechanism: REOPEN_MECHANISM.NONE,
|
|
429
|
+
handleKind: HANDLE_KIND.SESSION
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
function capabilitiesOf(channel) {
|
|
433
|
+
return CHANNEL_CAPABILITIES[channel ?? DEFAULT_CONVERSATION_CHANNEL];
|
|
434
|
+
}
|
|
435
|
+
var CHANNEL_FILTER_ALL = "all";
|
|
436
|
+
function channelFiltersFor(conversations) {
|
|
437
|
+
const present = new Set(
|
|
438
|
+
conversations.map((conversation) => conversation.channel ?? DEFAULT_CONVERSATION_CHANNEL)
|
|
439
|
+
);
|
|
440
|
+
if (present.size < 2) return [];
|
|
441
|
+
const ordered = Object.keys(CHANNEL_CAPABILITIES).filter((channel) => present.has(channel));
|
|
442
|
+
return [
|
|
443
|
+
{ value: CHANNEL_FILTER_ALL, label: "Todos" },
|
|
444
|
+
...ordered.map((channel) => ({ value: channel, label: CHANNEL_CAPABILITIES[channel].label }))
|
|
445
|
+
];
|
|
446
|
+
}
|
|
447
|
+
function formatContactHandle(params) {
|
|
448
|
+
const { handleKind } = capabilitiesOf(params.channel);
|
|
449
|
+
if (handleKind === HANDLE_KIND.PHONE) return formatPhone(params.handle);
|
|
450
|
+
if (handleKind === HANDLE_KIND.USERNAME) return params.handle.startsWith("@") ? params.handle : `@${params.handle}`;
|
|
451
|
+
return `Visitante ${params.handle.slice(-6)}`;
|
|
452
|
+
}
|
|
453
|
+
function contactFlag(params) {
|
|
454
|
+
return capabilitiesOf(params.channel).handleKind === HANDLE_KIND.PHONE ? phoneCountryFlag(params.handle) : "";
|
|
455
|
+
}
|
|
456
|
+
|
|
978
457
|
// src/ConversationListItem.tsx
|
|
979
|
-
import { jsx as
|
|
458
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
980
459
|
function formatRelativeTime(dateStr) {
|
|
981
460
|
try {
|
|
982
461
|
const date = new Date(dateStr);
|
|
@@ -1035,50 +514,60 @@ var ConversationListItem = ({
|
|
|
1035
514
|
active = false,
|
|
1036
515
|
selected = false,
|
|
1037
516
|
onClick,
|
|
1038
|
-
onSelect
|
|
517
|
+
onSelect,
|
|
518
|
+
showDivider = true,
|
|
519
|
+
highlightActive = true
|
|
1039
520
|
}) => {
|
|
1040
521
|
const isActive = active || selected;
|
|
1041
522
|
const windowStatus = useMemo(() => getWindowStatus(conversation.lastInboundAt), [conversation.lastInboundAt]);
|
|
1042
523
|
const preview = useMemo(() => lastMessagePreview(conversation), [conversation.lastContent]);
|
|
1043
|
-
const
|
|
524
|
+
const handle = conversation.contactId ?? conversation.whatsappNumber;
|
|
525
|
+
const displayHandle = formatContactHandle({ handle, channel: conversation.channel });
|
|
526
|
+
const name = conversation.clientName || displayHandle || handle;
|
|
1044
527
|
const timestamp = formatRelativeTime(conversation.lastAt);
|
|
528
|
+
const flag = contactFlag({ handle, channel: conversation.channel });
|
|
1045
529
|
const isInbound = conversation.lastDirection === "inbound";
|
|
1046
530
|
const handleClick = () => {
|
|
1047
531
|
onClick?.();
|
|
1048
532
|
onSelect?.(conversation.id);
|
|
1049
533
|
};
|
|
1050
|
-
return /* @__PURE__ */
|
|
534
|
+
return /* @__PURE__ */ jsxs3(
|
|
1051
535
|
"button",
|
|
1052
536
|
{
|
|
1053
537
|
onClick: handleClick,
|
|
1054
|
-
className:
|
|
538
|
+
className: `w-full flex items-center gap-3 px-3 py-2.5 text-left transition-colors ${highlightActive ? "hover:bg-[#f0f2f5]" : ""} ${showDivider ? "border-b border-[#e9edef]" : ""}`,
|
|
1055
539
|
style: {
|
|
1056
|
-
backgroundColor: isActive ? "#f0f2f5" : "transparent"
|
|
540
|
+
backgroundColor: isActive && highlightActive ? "#f0f2f5" : "transparent"
|
|
1057
541
|
},
|
|
1058
542
|
children: [
|
|
1059
|
-
/* @__PURE__ */
|
|
1060
|
-
/* @__PURE__ */
|
|
1061
|
-
conversation.waitingHuman && /* @__PURE__ */
|
|
543
|
+
/* @__PURE__ */ jsxs3("div", { className: "relative flex-shrink-0", children: [
|
|
544
|
+
/* @__PURE__ */ jsx4(Avatar, { name: conversation.clientName, size: "lg" }),
|
|
545
|
+
conversation.waitingHuman && /* @__PURE__ */ jsx4("div", { className: "absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 bg-amber-400 rounded-full border-2 border-white" })
|
|
1062
546
|
] }),
|
|
1063
|
-
/* @__PURE__ */
|
|
1064
|
-
/* @__PURE__ */
|
|
1065
|
-
/* @__PURE__ */
|
|
1066
|
-
/* @__PURE__ */
|
|
1067
|
-
|
|
1068
|
-
windowStatus && windowStatus.label === "
|
|
547
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
|
|
548
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between gap-2", children: [
|
|
549
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5 min-w-0", children: [
|
|
550
|
+
!conversation.clientName && flag ? /* @__PURE__ */ jsx4("span", { "aria-hidden": true, children: flag }) : null,
|
|
551
|
+
/* @__PURE__ */ jsx4("span", { className: "text-[16px] text-[#111b21] truncate", children: name }),
|
|
552
|
+
windowStatus && windowStatus.label === "expired" && /* @__PURE__ */ jsx4("span", { className: "w-2 h-2 rounded-full bg-red-500 flex-shrink-0", title: "Janela expirada" }),
|
|
553
|
+
windowStatus && windowStatus.label === "warning" && /* @__PURE__ */ jsx4("span", { className: "w-2 h-2 rounded-full bg-orange-500 flex-shrink-0", title: "Janela pr\xF3xima do fim" })
|
|
1069
554
|
] }),
|
|
1070
|
-
timestamp && /* @__PURE__ */
|
|
555
|
+
timestamp && /* @__PURE__ */ jsx4("span", { className: "text-xs text-[#667781] flex-shrink-0", children: timestamp })
|
|
1071
556
|
] }),
|
|
1072
|
-
/* @__PURE__ */
|
|
1073
|
-
/* @__PURE__ */
|
|
1074
|
-
|
|
557
|
+
conversation.clientName ? /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 text-xs text-[#667781]", children: [
|
|
558
|
+
flag ? /* @__PURE__ */ jsx4("span", { "aria-hidden": true, children: flag }) : null,
|
|
559
|
+
/* @__PURE__ */ jsx4("span", { className: "truncate", children: displayHandle })
|
|
560
|
+
] }) : null,
|
|
561
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between mt-0.5", children: [
|
|
562
|
+
/* @__PURE__ */ jsxs3("span", { className: "text-sm text-[#667781] truncate max-w-[180px]", children: [
|
|
563
|
+
preview.icon && /* @__PURE__ */ jsx4("span", { className: "mr-1", children: preview.icon }),
|
|
1075
564
|
preview.preview || "Sem mensagens"
|
|
1076
565
|
] }),
|
|
1077
|
-
conversation.unread > 0 && /* @__PURE__ */
|
|
566
|
+
conversation.unread > 0 && /* @__PURE__ */ jsx4("span", { className: "bg-[#25d366] text-white text-xs font-semibold rounded-full min-w-[20px] h-5 px-1.5 flex items-center justify-center flex-shrink-0 ml-2", children: conversation.unread > 99 ? "99+" : conversation.unread })
|
|
1078
567
|
] }),
|
|
1079
|
-
/* @__PURE__ */
|
|
1080
|
-
conversation.mode === "human" && /* @__PURE__ */
|
|
1081
|
-
conversation.mode === "bot" && conversation.waitingHuman && /* @__PURE__ */
|
|
568
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5 mt-1", children: [
|
|
569
|
+
conversation.mode === "human" && /* @__PURE__ */ jsx4("span", { className: "text-[10px] px-1.5 py-0.5 rounded font-medium bg-purple-100 text-purple-700", children: "Humano" }),
|
|
570
|
+
conversation.mode === "bot" && conversation.waitingHuman && /* @__PURE__ */ jsx4("span", { className: "text-[10px] px-1.5 py-0.5 rounded font-medium bg-amber-100 text-amber-700", children: "Aguardando" })
|
|
1082
571
|
] })
|
|
1083
572
|
] })
|
|
1084
573
|
]
|
|
@@ -1087,9 +576,9 @@ var ConversationListItem = ({
|
|
|
1087
576
|
};
|
|
1088
577
|
|
|
1089
578
|
// src/Toast.tsx
|
|
1090
|
-
import { createContext
|
|
1091
|
-
import { jsx as
|
|
1092
|
-
var ToastContext =
|
|
579
|
+
import { createContext, useContext, useState as useState2, useCallback, useEffect as useEffect2 } from "react";
|
|
580
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
581
|
+
var ToastContext = createContext(null);
|
|
1093
582
|
var nextId = 0;
|
|
1094
583
|
var singletonShow = null;
|
|
1095
584
|
var toast = {
|
|
@@ -1105,34 +594,34 @@ var typeStyles = {
|
|
|
1105
594
|
info: "bg-blue-600"
|
|
1106
595
|
};
|
|
1107
596
|
var typeIcons = {
|
|
1108
|
-
success: /* @__PURE__ */
|
|
1109
|
-
error: /* @__PURE__ */
|
|
1110
|
-
/* @__PURE__ */
|
|
1111
|
-
/* @__PURE__ */
|
|
597
|
+
success: /* @__PURE__ */ jsx5("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx5("polyline", { points: "20 6 9 17 4 12" }) }),
|
|
598
|
+
error: /* @__PURE__ */ jsxs4("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
599
|
+
/* @__PURE__ */ jsx5("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
600
|
+
/* @__PURE__ */ jsx5("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1112
601
|
] }),
|
|
1113
|
-
info: /* @__PURE__ */
|
|
1114
|
-
/* @__PURE__ */
|
|
1115
|
-
/* @__PURE__ */
|
|
1116
|
-
/* @__PURE__ */
|
|
602
|
+
info: /* @__PURE__ */ jsxs4("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
603
|
+
/* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "10" }),
|
|
604
|
+
/* @__PURE__ */ jsx5("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
|
|
605
|
+
/* @__PURE__ */ jsx5("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
|
|
1117
606
|
] })
|
|
1118
607
|
};
|
|
1119
608
|
function ToastItemComponent({ item, onRemove }) {
|
|
1120
|
-
const [exiting, setExiting] =
|
|
1121
|
-
|
|
609
|
+
const [exiting, setExiting] = useState2(false);
|
|
610
|
+
useEffect2(() => {
|
|
1122
611
|
const timer = setTimeout(() => {
|
|
1123
612
|
setExiting(true);
|
|
1124
613
|
setTimeout(() => onRemove(item.id), 300);
|
|
1125
614
|
}, 5e3);
|
|
1126
615
|
return () => clearTimeout(timer);
|
|
1127
616
|
}, [item.id, onRemove]);
|
|
1128
|
-
return /* @__PURE__ */
|
|
617
|
+
return /* @__PURE__ */ jsxs4(
|
|
1129
618
|
"div",
|
|
1130
619
|
{
|
|
1131
620
|
className: `flex items-center gap-2 px-4 py-3 rounded-lg shadow-lg text-white text-sm min-w-[280px] max-w-[400px] ${typeStyles[item.type]} ${exiting ? "animate-slide-out" : "animate-slide-in"}`,
|
|
1132
621
|
children: [
|
|
1133
|
-
/* @__PURE__ */
|
|
1134
|
-
/* @__PURE__ */
|
|
1135
|
-
/* @__PURE__ */
|
|
622
|
+
/* @__PURE__ */ jsx5("span", { className: "flex-shrink-0", children: typeIcons[item.type] }),
|
|
623
|
+
/* @__PURE__ */ jsx5("span", { className: "flex-1", children: item.message }),
|
|
624
|
+
/* @__PURE__ */ jsx5(
|
|
1136
625
|
"button",
|
|
1137
626
|
{
|
|
1138
627
|
onClick: () => {
|
|
@@ -1140,9 +629,9 @@ function ToastItemComponent({ item, onRemove }) {
|
|
|
1140
629
|
setTimeout(() => onRemove(item.id), 300);
|
|
1141
630
|
},
|
|
1142
631
|
className: "flex-shrink-0 opacity-70 hover:opacity-100 transition-opacity",
|
|
1143
|
-
children: /* @__PURE__ */
|
|
1144
|
-
/* @__PURE__ */
|
|
1145
|
-
/* @__PURE__ */
|
|
632
|
+
children: /* @__PURE__ */ jsxs4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
633
|
+
/* @__PURE__ */ jsx5("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
634
|
+
/* @__PURE__ */ jsx5("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1146
635
|
] })
|
|
1147
636
|
}
|
|
1148
637
|
)
|
|
@@ -1151,24 +640,24 @@ function ToastItemComponent({ item, onRemove }) {
|
|
|
1151
640
|
);
|
|
1152
641
|
}
|
|
1153
642
|
function ToastProvider({ children }) {
|
|
1154
|
-
const [items, setItems] =
|
|
1155
|
-
const show =
|
|
643
|
+
const [items, setItems] = useState2([]);
|
|
644
|
+
const show = useCallback((type, message) => {
|
|
1156
645
|
const id = ++nextId;
|
|
1157
646
|
setItems((prev) => [...prev, { id, type, message }]);
|
|
1158
647
|
}, []);
|
|
1159
|
-
const remove =
|
|
648
|
+
const remove = useCallback((id) => {
|
|
1160
649
|
setItems((prev) => prev.filter((item) => item.id !== id));
|
|
1161
650
|
}, []);
|
|
1162
|
-
|
|
651
|
+
useEffect2(() => {
|
|
1163
652
|
singletonShow = show;
|
|
1164
653
|
return () => {
|
|
1165
654
|
singletonShow = null;
|
|
1166
655
|
};
|
|
1167
656
|
}, [show]);
|
|
1168
|
-
return /* @__PURE__ */
|
|
657
|
+
return /* @__PURE__ */ jsxs4(ToastContext.Provider, { value: { show }, children: [
|
|
1169
658
|
children,
|
|
1170
|
-
/* @__PURE__ */
|
|
1171
|
-
/* @__PURE__ */
|
|
659
|
+
/* @__PURE__ */ jsx5("div", { className: "fixed top-4 right-4 z-[100] flex flex-col gap-2", children: items.map((item) => /* @__PURE__ */ jsx5(ToastItemComponent, { item, onRemove: remove }, item.id)) }),
|
|
660
|
+
/* @__PURE__ */ jsx5("style", { children: `
|
|
1172
661
|
@keyframes slide-in {
|
|
1173
662
|
from { transform: translateX(100%); opacity: 0; }
|
|
1174
663
|
to { transform: translateX(0); opacity: 1; }
|
|
@@ -1183,7 +672,7 @@ function ToastProvider({ children }) {
|
|
|
1183
672
|
] });
|
|
1184
673
|
}
|
|
1185
674
|
function useToast() {
|
|
1186
|
-
const context =
|
|
675
|
+
const context = useContext(ToastContext);
|
|
1187
676
|
if (!context) {
|
|
1188
677
|
throw new Error("useToast must be used within a ToastProvider");
|
|
1189
678
|
}
|
|
@@ -1191,11 +680,11 @@ function useToast() {
|
|
|
1191
680
|
}
|
|
1192
681
|
|
|
1193
682
|
// src/MessageText.tsx
|
|
1194
|
-
import { useCallback as
|
|
1195
|
-
import { jsx as
|
|
683
|
+
import { useCallback as useCallback2, useState as useState3 } from "react";
|
|
684
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1196
685
|
function MessageText({ message }) {
|
|
1197
|
-
const [copied, setCopied] =
|
|
1198
|
-
const handleCopy =
|
|
686
|
+
const [copied, setCopied] = useState3(false);
|
|
687
|
+
const handleCopy = useCallback2(async () => {
|
|
1199
688
|
if (!message.content) return;
|
|
1200
689
|
try {
|
|
1201
690
|
await navigator.clipboard.writeText(message.content);
|
|
@@ -1205,62 +694,611 @@ function MessageText({ message }) {
|
|
|
1205
694
|
}
|
|
1206
695
|
}, [message.content]);
|
|
1207
696
|
if (message.type === "template")
|
|
1208
|
-
return /* @__PURE__ */
|
|
1209
|
-
return /* @__PURE__ */
|
|
697
|
+
return /* @__PURE__ */ jsx6("p", { className: "text-sm italic text-[#667781]", children: message.content ?? "Template message" });
|
|
698
|
+
return /* @__PURE__ */ jsxs5(
|
|
1210
699
|
"div",
|
|
1211
700
|
{
|
|
1212
701
|
onClick: handleCopy,
|
|
1213
702
|
className: "text-[14.2px] leading-[19px] whitespace-pre-wrap break-words select-all [&_strong]:font-bold [&_em]:italic [&_del]:line-through",
|
|
1214
703
|
children: [
|
|
1215
|
-
/* @__PURE__ */
|
|
1216
|
-
copied && /* @__PURE__ */
|
|
704
|
+
/* @__PURE__ */ jsx6("span", { children: parseWhatsAppFormatting(message.content ?? "") }),
|
|
705
|
+
copied && /* @__PURE__ */ jsx6("span", { className: "absolute top-0 right-0 -translate-y-full bg-[#3b4a54] text-white text-[11px] px-1.5 py-0.5 rounded shadow-lg", children: "Copiado!" })
|
|
1217
706
|
]
|
|
1218
707
|
}
|
|
1219
708
|
);
|
|
1220
709
|
}
|
|
1221
710
|
|
|
1222
711
|
// src/MessageTimestamp.tsx
|
|
1223
|
-
import { jsx as
|
|
712
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1224
713
|
function MessageTimestamp({ timestamp, status, isOutbound }) {
|
|
1225
|
-
return /* @__PURE__ */
|
|
1226
|
-
/* @__PURE__ */
|
|
1227
|
-
isOutbound && status && /* @__PURE__ */
|
|
714
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-end gap-[3px]", children: [
|
|
715
|
+
/* @__PURE__ */ jsx7("span", { className: "text-[11px] text-[#667781] leading-[15px] select-none", children: timestamp }),
|
|
716
|
+
isOutbound && status && /* @__PURE__ */ jsx7("span", { className: "flex items-center -mr-[2px]", children: /* @__PURE__ */ jsx7(StatusTicks, { status }) })
|
|
1228
717
|
] });
|
|
1229
718
|
}
|
|
1230
719
|
|
|
1231
720
|
// src/MessageTail.tsx
|
|
1232
|
-
import { jsx as
|
|
721
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1233
722
|
function MessageTail({ isOutbound }) {
|
|
1234
723
|
if (isOutbound)
|
|
1235
|
-
return /* @__PURE__ */
|
|
1236
|
-
return /* @__PURE__ */
|
|
724
|
+
return /* @__PURE__ */ jsx8("svg", { className: "absolute top-0 -right-[5px] w-[5px] h-[11px]", viewBox: "0 0 5 11", fill: "#d9fdd3", children: /* @__PURE__ */ jsx8("path", { d: "M5 0H0v11c1.5-2 4.5-2 5-4V0z" }) });
|
|
725
|
+
return /* @__PURE__ */ jsx8("svg", { className: "absolute top-0 -left-[5px] w-[5px] h-[11px]", viewBox: "0 0 5 11", fill: "white", children: /* @__PURE__ */ jsx8("path", { d: "M0 0h5v11C3.5 9 .5 9 0 7V0z" }) });
|
|
1237
726
|
}
|
|
1238
727
|
|
|
1239
|
-
// src/
|
|
1240
|
-
|
|
728
|
+
// src/conversationWindow.ts
|
|
729
|
+
var MINUTE_MS = 6e4;
|
|
730
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
731
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
732
|
+
var CONVERSATION_WINDOW = {
|
|
733
|
+
ALL: "all",
|
|
734
|
+
FRESH: "fresh",
|
|
735
|
+
WARNING: "warning",
|
|
736
|
+
CRITICAL: "critical",
|
|
737
|
+
EXPIRED: "expired"
|
|
738
|
+
};
|
|
739
|
+
var WINDOW_FILTERS = [
|
|
740
|
+
{ value: CONVERSATION_WINDOW.ALL, label: "Todas", dotClass: "" },
|
|
741
|
+
{ value: CONVERSATION_WINDOW.FRESH, label: "<12h", dotClass: "bg-green-500" },
|
|
742
|
+
{ value: CONVERSATION_WINDOW.WARNING, label: "12-21h", dotClass: "bg-yellow-500" },
|
|
743
|
+
{ value: CONVERSATION_WINDOW.CRITICAL, label: "21-24h", dotClass: "bg-red-500" },
|
|
744
|
+
{ value: CONVERSATION_WINDOW.EXPIRED, label: ">24h", dotClass: "bg-gray-400" }
|
|
745
|
+
];
|
|
746
|
+
var WARNING_RATIO = 0.5;
|
|
747
|
+
var CRITICAL_RATIO = 0.875;
|
|
748
|
+
function windowOf(params) {
|
|
749
|
+
const capabilities = capabilitiesOf(params.channel);
|
|
750
|
+
if (!capabilities.hasSessionWindow) return CONVERSATION_WINDOW.FRESH;
|
|
751
|
+
if (!params.lastInboundAt) return CONVERSATION_WINDOW.EXPIRED;
|
|
752
|
+
const windowMs = capabilities.windowHours * HOUR_MS;
|
|
753
|
+
const elapsed = params.now - new Date(params.lastInboundAt).getTime();
|
|
754
|
+
if (elapsed >= windowMs) return CONVERSATION_WINDOW.EXPIRED;
|
|
755
|
+
if (elapsed >= windowMs * CRITICAL_RATIO) return CONVERSATION_WINDOW.CRITICAL;
|
|
756
|
+
if (elapsed >= windowMs * WARNING_RATIO) return CONVERSATION_WINDOW.WARNING;
|
|
757
|
+
return CONVERSATION_WINDOW.FRESH;
|
|
758
|
+
}
|
|
759
|
+
function formatStalledFor(lastAt, now) {
|
|
760
|
+
const elapsed = Math.max(0, now - new Date(lastAt).getTime());
|
|
761
|
+
const days = Math.floor(elapsed / DAY_MS);
|
|
762
|
+
const hours = Math.floor(elapsed % DAY_MS / HOUR_MS);
|
|
763
|
+
const minutes = Math.floor(elapsed % HOUR_MS / MINUTE_MS);
|
|
764
|
+
if (days > 0) return `${days}d ${hours}h:${String(minutes).padStart(2, "0")}m`;
|
|
765
|
+
if (hours > 0) return `${hours}h:${String(minutes).padStart(2, "0")}m`;
|
|
766
|
+
return `${minutes}m`;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/ChannelIcon.tsx
|
|
770
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
771
|
+
var CHANNEL_BRAND_COLOR = {
|
|
772
|
+
[CONVERSATION_CHANNEL.WHATSAPP]: "#25D366",
|
|
773
|
+
[CONVERSATION_CHANNEL.MESSENGER]: "#0084FF",
|
|
774
|
+
[CONVERSATION_CHANNEL.INSTAGRAM]: "#E4405F",
|
|
775
|
+
[CONVERSATION_CHANNEL.WEBCHAT]: "#64748B"
|
|
776
|
+
};
|
|
777
|
+
function Glyph({ channel }) {
|
|
778
|
+
const color = CHANNEL_BRAND_COLOR[channel];
|
|
779
|
+
if (channel === CONVERSATION_CHANNEL.WHATSAPP) {
|
|
780
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
781
|
+
/* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
782
|
+
/* @__PURE__ */ jsx9(
|
|
783
|
+
"path",
|
|
784
|
+
{
|
|
785
|
+
d: "M8.6 7.2c.3-.1.7 0 .9.3l1 1.6c.2.3.1.7-.1.9l-.7.7c.6 1.3 1.6 2.3 2.9 2.9l.7-.7c.2-.2.6-.3.9-.1l1.6 1c.3.2.4.6.3.9-.4 1-1.4 1.6-2.4 1.4-3-.5-5.4-2.9-5.9-5.9-.2-1 .4-2 1.4-2.4Z",
|
|
786
|
+
fill: "#fff"
|
|
787
|
+
}
|
|
788
|
+
)
|
|
789
|
+
] });
|
|
790
|
+
}
|
|
791
|
+
if (channel === CONVERSATION_CHANNEL.MESSENGER) {
|
|
792
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
793
|
+
/* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
794
|
+
/* @__PURE__ */ jsx9("path", { d: "M6.5 15.5 11 10.8l2.4 2.4L17.5 9l-4.6 4.9-2.4-2.4-4 4Z", fill: "#fff", stroke: "#fff", strokeWidth: "1.4", strokeLinejoin: "round" })
|
|
795
|
+
] });
|
|
796
|
+
}
|
|
797
|
+
if (channel === CONVERSATION_CHANNEL.INSTAGRAM) {
|
|
798
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
799
|
+
/* @__PURE__ */ jsx9("rect", { x: "2", y: "2", width: "20", height: "20", rx: "6", fill: color }),
|
|
800
|
+
/* @__PURE__ */ jsx9("rect", { x: "6.5", y: "6.5", width: "11", height: "11", rx: "3.5", fill: "none", stroke: "#fff", strokeWidth: "1.6" }),
|
|
801
|
+
/* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "2.6", fill: "none", stroke: "#fff", strokeWidth: "1.6" }),
|
|
802
|
+
/* @__PURE__ */ jsx9("circle", { cx: "16.4", cy: "7.6", r: "1", fill: "#fff" })
|
|
803
|
+
] });
|
|
804
|
+
}
|
|
805
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
806
|
+
/* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
807
|
+
/* @__PURE__ */ jsx9("path", { d: "M6.5 8.5h11v7h-5.5L8.5 18v-2.5h-2v-7Z", fill: "#fff" })
|
|
808
|
+
] });
|
|
809
|
+
}
|
|
810
|
+
function ChannelIcon({ channel, size = 14, className = "" }) {
|
|
811
|
+
const resolved = channel ?? CONVERSATION_CHANNEL.WHATSAPP;
|
|
812
|
+
const { label } = capabilitiesOf(resolved);
|
|
813
|
+
return /* @__PURE__ */ jsx9(
|
|
814
|
+
"svg",
|
|
815
|
+
{
|
|
816
|
+
viewBox: "0 0 24 24",
|
|
817
|
+
width: size,
|
|
818
|
+
height: size,
|
|
819
|
+
className: `inline-block shrink-0 ${className}`.trim(),
|
|
820
|
+
role: "img",
|
|
821
|
+
"aria-label": label,
|
|
822
|
+
children: /* @__PURE__ */ jsx9(Glyph, { channel: resolved })
|
|
823
|
+
}
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/ConversationRow.tsx
|
|
828
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
829
|
+
var WINDOW_TITLE = {
|
|
830
|
+
[CONVERSATION_WINDOW.ALL]: "",
|
|
831
|
+
[CONVERSATION_WINDOW.FRESH]: "Janela de 24h aberta (menos de 12h)",
|
|
832
|
+
[CONVERSATION_WINDOW.WARNING]: "Janela de 24h fechando (12-21h)",
|
|
833
|
+
[CONVERSATION_WINDOW.CRITICAL]: "Janela de 24h quase expirada (21-24h)",
|
|
834
|
+
[CONVERSATION_WINDOW.EXPIRED]: "Janela de 24h expirada \u2014 s\xF3 template"
|
|
835
|
+
};
|
|
836
|
+
var STALLED_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
837
|
+
var WINDOW_BAR_CLASS = {
|
|
838
|
+
[CONVERSATION_WINDOW.ALL]: "bg-transparent",
|
|
839
|
+
[CONVERSATION_WINDOW.FRESH]: "bg-green-500",
|
|
840
|
+
[CONVERSATION_WINDOW.WARNING]: "bg-yellow-500",
|
|
841
|
+
[CONVERSATION_WINDOW.CRITICAL]: "bg-red-500",
|
|
842
|
+
[CONVERSATION_WINDOW.EXPIRED]: "bg-gray-300"
|
|
843
|
+
};
|
|
844
|
+
function ConversationRow({
|
|
845
|
+
conversation,
|
|
846
|
+
active,
|
|
847
|
+
selected,
|
|
848
|
+
now,
|
|
849
|
+
busy,
|
|
850
|
+
onOpen,
|
|
851
|
+
onToggleSelected,
|
|
852
|
+
onTakeover,
|
|
853
|
+
className,
|
|
854
|
+
classNames
|
|
855
|
+
}) {
|
|
856
|
+
const capabilities = capabilitiesOf(conversation.channel);
|
|
857
|
+
const window2 = windowOf({ lastInboundAt: conversation.lastInboundAt, now, channel: conversation.channel });
|
|
858
|
+
const stalledMs = now - new Date(conversation.lastAt).getTime();
|
|
859
|
+
const isStalled = stalledMs > STALLED_THRESHOLD_MS;
|
|
860
|
+
const isWaiting = conversation.mode === "bot" && conversation.waitingHuman;
|
|
861
|
+
return /* @__PURE__ */ jsxs8("div", { className: cn("cv-row flex border-b", active && "cv-row--active", classNames?.root, className), children: [
|
|
862
|
+
/* @__PURE__ */ jsx10(
|
|
863
|
+
"span",
|
|
864
|
+
{
|
|
865
|
+
className: cn("w-1 shrink-0", WINDOW_BAR_CLASS[window2], classNames?.windowBar),
|
|
866
|
+
title: WINDOW_TITLE[window2],
|
|
867
|
+
"aria-label": WINDOW_TITLE[window2]
|
|
868
|
+
}
|
|
869
|
+
),
|
|
870
|
+
/* @__PURE__ */ jsxs8("div", { className: "min-w-0 flex-1", children: [
|
|
871
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-start gap-2 px-2 pt-2", children: [
|
|
872
|
+
/* @__PURE__ */ jsx10(
|
|
873
|
+
"input",
|
|
874
|
+
{
|
|
875
|
+
type: "checkbox",
|
|
876
|
+
checked: selected,
|
|
877
|
+
onChange: onToggleSelected,
|
|
878
|
+
"aria-label": `Selecionar conversa ${conversation.whatsappNumber}`,
|
|
879
|
+
className: "mt-3"
|
|
880
|
+
}
|
|
881
|
+
),
|
|
882
|
+
/* @__PURE__ */ jsx10("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx10(
|
|
883
|
+
ConversationListItem,
|
|
884
|
+
{
|
|
885
|
+
conversation,
|
|
886
|
+
active,
|
|
887
|
+
onClick: onOpen,
|
|
888
|
+
showDivider: false,
|
|
889
|
+
highlightActive: false
|
|
890
|
+
}
|
|
891
|
+
) })
|
|
892
|
+
] }),
|
|
893
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-center gap-1.5 px-4 pb-2 pl-8", children: [
|
|
894
|
+
/* @__PURE__ */ jsxs8("span", { className: "cv-pill", children: [
|
|
895
|
+
/* @__PURE__ */ jsx10(ChannelIcon, { channel: conversation.channel }),
|
|
896
|
+
" ",
|
|
897
|
+
capabilities.label
|
|
898
|
+
] }),
|
|
899
|
+
conversation.mode === "human" ? /* @__PURE__ */ jsx10("span", { className: "cv-pill cv-pill--success", children: "\u{1F9D1}\u200D\u{1F4BC} em atendimento" }) : null,
|
|
900
|
+
isWaiting ? /* @__PURE__ */ jsx10("span", { className: "cv-pill cv-pill--warning", children: "\u23F3 aguardando atendimento" }) : null,
|
|
901
|
+
!isWaiting && conversation.mode === "bot" ? /* @__PURE__ */ jsx10("span", { className: "cv-pill", children: "\u{1F916} bot ativo" }) : null,
|
|
902
|
+
isStalled ? /* @__PURE__ */ jsxs8("span", { className: "cv-pill cv-pill--danger", children: [
|
|
903
|
+
"\u23F1\uFE0F parada h\xE1 ",
|
|
904
|
+
formatStalledFor(conversation.lastAt, now)
|
|
905
|
+
] }) : null,
|
|
906
|
+
isWaiting ? /* @__PURE__ */ jsx10("button", { type: "button", onClick: onTakeover, disabled: busy, className: "cv-header-action", children: "\u25B6\uFE0F Continuar Atendimento" }) : null
|
|
907
|
+
] })
|
|
908
|
+
] })
|
|
909
|
+
] });
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/ConversationHeader.tsx
|
|
913
|
+
import { useState as useState4 } from "react";
|
|
914
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
915
|
+
var DEFAULT_CONVERSATION_HEADER_LABELS = {
|
|
916
|
+
botMode: "\u{1F916} atendimento autom\xE1tico",
|
|
917
|
+
humanMode: "\u{1F9D1}\u200D\u{1F4BC} atendimento humano",
|
|
918
|
+
returnToBot: "Devolver ao bot",
|
|
919
|
+
finish: "Finalizar",
|
|
920
|
+
takeover: "Assumir atendimento",
|
|
921
|
+
download: "Baixar conversa",
|
|
922
|
+
documents: "Arquivos da conversa",
|
|
923
|
+
back: "Voltar para a lista",
|
|
924
|
+
moreActions: "Mais a\xE7\xF5es"
|
|
925
|
+
};
|
|
926
|
+
function ConversationHeader({
|
|
927
|
+
conversation,
|
|
928
|
+
busy = false,
|
|
929
|
+
onTakeover,
|
|
930
|
+
onReturnToBot,
|
|
931
|
+
onFinish,
|
|
932
|
+
onDownload,
|
|
933
|
+
onOpenDocuments,
|
|
934
|
+
documentsOpen = false,
|
|
935
|
+
onBack,
|
|
936
|
+
labels: labelsOverride,
|
|
937
|
+
className,
|
|
938
|
+
classNames
|
|
939
|
+
}) {
|
|
940
|
+
const labels = { ...DEFAULT_CONVERSATION_HEADER_LABELS, ...labelsOverride };
|
|
941
|
+
const isHuman = conversation.mode === "human";
|
|
942
|
+
const handle = conversation.contactId ?? conversation.whatsappNumber;
|
|
943
|
+
const displayHandle = formatContactHandle({ handle, channel: conversation.channel });
|
|
944
|
+
const flag = contactFlag({ handle, channel: conversation.channel });
|
|
945
|
+
const capabilities = capabilitiesOf(conversation.channel);
|
|
946
|
+
const [menuOpen, setMenuOpen] = useState4(false);
|
|
947
|
+
const utilities = [
|
|
948
|
+
onOpenDocuments ? { key: "documents", icon: "\u{1F4C4}", label: labels.documents, run: onOpenDocuments, active: documentsOpen } : void 0,
|
|
949
|
+
onDownload ? { key: "download", icon: "\u2B07\uFE0F", label: labels.download, run: onDownload, active: false } : void 0
|
|
950
|
+
].filter(
|
|
951
|
+
(utility) => Boolean(utility)
|
|
952
|
+
);
|
|
953
|
+
const actions = [
|
|
954
|
+
!isHuman && onTakeover ? { key: "takeover", label: labels.takeover, run: onTakeover, className: "cv-header-action--primary" } : void 0,
|
|
955
|
+
isHuman && onReturnToBot ? { key: "release", label: labels.returnToBot, run: onReturnToBot, className: "" } : void 0,
|
|
956
|
+
isHuman && onFinish ? { key: "finish", label: `\u2715 ${labels.finish}`, run: onFinish, className: "cv-header-action--danger" } : void 0
|
|
957
|
+
].filter((action) => Boolean(action));
|
|
958
|
+
const menuItems = [
|
|
959
|
+
...utilities.map((utility) => ({
|
|
960
|
+
key: utility.key,
|
|
961
|
+
label: `${utility.icon} ${utility.label}`,
|
|
962
|
+
run: utility.run,
|
|
963
|
+
className: ""
|
|
964
|
+
})),
|
|
965
|
+
...actions
|
|
966
|
+
];
|
|
967
|
+
return /* @__PURE__ */ jsxs9(
|
|
968
|
+
"header",
|
|
969
|
+
{
|
|
970
|
+
className: cn(
|
|
971
|
+
"flex flex-nowrap items-center justify-between gap-3 border-b px-4 py-3",
|
|
972
|
+
classNames?.root,
|
|
973
|
+
className
|
|
974
|
+
),
|
|
975
|
+
children: [
|
|
976
|
+
/* @__PURE__ */ jsxs9("div", { className: cn("flex min-w-0 flex-1 items-center gap-3", classNames?.identity), children: [
|
|
977
|
+
onBack ? /* @__PURE__ */ jsx11("button", { type: "button", onClick: onBack, className: "cv-back cv-touch", "aria-label": labels.back, title: labels.back, children: "\u2190" }) : null,
|
|
978
|
+
/* @__PURE__ */ jsx11(Avatar, { name: conversation.clientName, size: "md" }),
|
|
979
|
+
/* @__PURE__ */ jsxs9("div", { className: "min-w-0", children: [
|
|
980
|
+
/* @__PURE__ */ jsx11("p", { className: cn("truncate font-medium", classNames?.name), children: conversation.clientName ?? displayHandle }),
|
|
981
|
+
/* @__PURE__ */ jsxs9("p", { className: cn("truncate text-xs text-gray-500", classNames?.meta), children: [
|
|
982
|
+
flag ? `${flag} ` : "",
|
|
983
|
+
displayHandle,
|
|
984
|
+
/* @__PURE__ */ jsxs9("span", { className: "ml-2 inline-flex items-center gap-1", children: [
|
|
985
|
+
/* @__PURE__ */ jsx11(ChannelIcon, { channel: conversation.channel }),
|
|
986
|
+
/* @__PURE__ */ jsx11("span", { className: "cv-only-wide", children: capabilities.label })
|
|
987
|
+
] }),
|
|
988
|
+
/* @__PURE__ */ jsx11("span", { className: "ml-2 cv-only-wide", children: isHuman ? labels.humanMode : labels.botMode }),
|
|
989
|
+
/* @__PURE__ */ jsx11("span", { className: "ml-1 cv-only-narrow", title: isHuman ? labels.humanMode : labels.botMode, children: isHuman ? "\u{1F9D1}\u200D\u{1F4BC}" : "\u{1F916}" })
|
|
990
|
+
] })
|
|
991
|
+
] })
|
|
992
|
+
] }),
|
|
993
|
+
/* @__PURE__ */ jsxs9("div", { className: cn("flex shrink-0 items-center gap-2", classNames?.actions), children: [
|
|
994
|
+
/* @__PURE__ */ jsxs9("div", { className: cn("hidden items-center gap-2 lg:flex", classNames?.desktopActions), children: [
|
|
995
|
+
utilities.map((utility) => /* @__PURE__ */ jsx11(
|
|
996
|
+
"button",
|
|
997
|
+
{
|
|
998
|
+
type: "button",
|
|
999
|
+
onClick: utility.run,
|
|
1000
|
+
disabled: busy,
|
|
1001
|
+
"aria-pressed": utility.active,
|
|
1002
|
+
title: utility.label,
|
|
1003
|
+
"aria-label": utility.label,
|
|
1004
|
+
className: `cv-header-icon ${utility.active ? "cv-header-icon--active" : ""}`,
|
|
1005
|
+
children: utility.icon
|
|
1006
|
+
},
|
|
1007
|
+
utility.key
|
|
1008
|
+
)),
|
|
1009
|
+
actions.map((action) => /* @__PURE__ */ jsx11(
|
|
1010
|
+
"button",
|
|
1011
|
+
{
|
|
1012
|
+
type: "button",
|
|
1013
|
+
onClick: action.run,
|
|
1014
|
+
disabled: busy,
|
|
1015
|
+
className: `cv-header-action ${action.className}`,
|
|
1016
|
+
children: action.label
|
|
1017
|
+
},
|
|
1018
|
+
action.key
|
|
1019
|
+
))
|
|
1020
|
+
] }),
|
|
1021
|
+
menuItems.length > 0 ? /* @__PURE__ */ jsxs9("div", { className: cn("cv-menu lg:hidden", classNames?.mobileMenu), children: [
|
|
1022
|
+
/* @__PURE__ */ jsx11(
|
|
1023
|
+
"button",
|
|
1024
|
+
{
|
|
1025
|
+
type: "button",
|
|
1026
|
+
onClick: () => setMenuOpen(!menuOpen),
|
|
1027
|
+
"aria-expanded": menuOpen,
|
|
1028
|
+
"aria-label": labels.moreActions,
|
|
1029
|
+
title: labels.moreActions,
|
|
1030
|
+
className: "cv-header-icon cv-touch",
|
|
1031
|
+
children: "\u22EE"
|
|
1032
|
+
}
|
|
1033
|
+
),
|
|
1034
|
+
menuOpen ? /* @__PURE__ */ jsx11("div", { className: "cv-menu-panel", role: "menu", children: menuItems.map((item) => /* @__PURE__ */ jsx11(
|
|
1035
|
+
"button",
|
|
1036
|
+
{
|
|
1037
|
+
type: "button",
|
|
1038
|
+
role: "menuitem",
|
|
1039
|
+
onClick: () => {
|
|
1040
|
+
setMenuOpen(false);
|
|
1041
|
+
item.run();
|
|
1042
|
+
},
|
|
1043
|
+
disabled: busy,
|
|
1044
|
+
className: `cv-header-action cv-touch ${item.className}`,
|
|
1045
|
+
children: item.label
|
|
1046
|
+
},
|
|
1047
|
+
item.key
|
|
1048
|
+
)) }) : null
|
|
1049
|
+
] }) : null
|
|
1050
|
+
] })
|
|
1051
|
+
]
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// src/ConversationContextPanel.tsx
|
|
1057
|
+
import { useState as useState6 } from "react";
|
|
1058
|
+
|
|
1059
|
+
// src/useIsNarrow.ts
|
|
1060
|
+
import { useEffect as useEffect3, useState as useState5 } from "react";
|
|
1061
|
+
var NARROW_MAX_WIDTH_PX = 1023;
|
|
1062
|
+
function useIsNarrow() {
|
|
1063
|
+
const [isNarrow, setIsNarrow] = useState5(
|
|
1064
|
+
() => typeof window !== "undefined" && window.matchMedia(`(max-width: ${NARROW_MAX_WIDTH_PX}px)`).matches
|
|
1065
|
+
);
|
|
1066
|
+
useEffect3(() => {
|
|
1067
|
+
const query = window.matchMedia(`(max-width: ${NARROW_MAX_WIDTH_PX}px)`);
|
|
1068
|
+
const handleChange = (event) => setIsNarrow(event.matches);
|
|
1069
|
+
query.addEventListener("change", handleChange);
|
|
1070
|
+
setIsNarrow(query.matches);
|
|
1071
|
+
return () => query.removeEventListener("change", handleChange);
|
|
1072
|
+
}, []);
|
|
1073
|
+
return isNarrow;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// src/ConversationContextPanel.tsx
|
|
1077
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1078
|
+
var DEFAULT_CONVERSATION_CONTEXT_LABELS = {
|
|
1079
|
+
title: "\u{1F4CB} Suas Sele\xE7\xF5es",
|
|
1080
|
+
empty: "Nada coletado ainda nesta conversa."
|
|
1081
|
+
};
|
|
1082
|
+
function ConversationContextPanel({
|
|
1083
|
+
entries,
|
|
1084
|
+
labels: labelsOverride,
|
|
1085
|
+
className,
|
|
1086
|
+
classNames
|
|
1087
|
+
}) {
|
|
1088
|
+
const labels = { ...DEFAULT_CONVERSATION_CONTEXT_LABELS, ...labelsOverride };
|
|
1089
|
+
const filled = entries.filter((entry) => Boolean(entry.value));
|
|
1090
|
+
const isNarrow = useIsNarrow();
|
|
1091
|
+
const [manualOpen, setManualOpen] = useState6(void 0);
|
|
1092
|
+
const open = manualOpen ?? (!isNarrow && filled.length > 0);
|
|
1093
|
+
return /* @__PURE__ */ jsxs10("section", { className: cn("border-b", classNames?.root, className), children: [
|
|
1094
|
+
/* @__PURE__ */ jsxs10(
|
|
1095
|
+
"button",
|
|
1096
|
+
{
|
|
1097
|
+
type: "button",
|
|
1098
|
+
onClick: () => setManualOpen(!open),
|
|
1099
|
+
"aria-expanded": open,
|
|
1100
|
+
className: cn("flex w-full items-center gap-2 px-4 py-3 text-left text-sm font-medium", classNames?.toggle),
|
|
1101
|
+
children: [
|
|
1102
|
+
/* @__PURE__ */ jsx12("span", { "aria-hidden": true, className: "text-xs", children: open ? "\u25BE" : "\u25B8" }),
|
|
1103
|
+
/* @__PURE__ */ jsx12("span", { children: labels.title }),
|
|
1104
|
+
/* @__PURE__ */ jsxs10("span", { className: cn("rounded-full bg-gray-200 px-2 text-xs dark:bg-gray-700", classNames?.counter), children: [
|
|
1105
|
+
filled.length,
|
|
1106
|
+
"/",
|
|
1107
|
+
entries.length
|
|
1108
|
+
] })
|
|
1109
|
+
]
|
|
1110
|
+
}
|
|
1111
|
+
),
|
|
1112
|
+
open ? /* @__PURE__ */ jsx12("div", { className: cn("px-4 pb-3", classNames?.body), children: entries.length === 0 ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-gray-500", children: labels.empty }) : /* @__PURE__ */ jsx12("dl", { className: "grid grid-cols-2 gap-2 text-xs", children: entries.map((entry) => /* @__PURE__ */ jsxs10("div", { className: "min-w-0", children: [
|
|
1113
|
+
/* @__PURE__ */ jsxs10("dt", { className: "truncate text-gray-500", children: [
|
|
1114
|
+
entry.icon ? `${entry.icon} ` : "",
|
|
1115
|
+
entry.label
|
|
1116
|
+
] }),
|
|
1117
|
+
/* @__PURE__ */ jsx12("dd", { className: "truncate font-medium", children: entry.value ? /* @__PURE__ */ jsxs10("span", { className: "text-green-700 dark:text-green-400", children: [
|
|
1118
|
+
"\u2713 ",
|
|
1119
|
+
entry.value
|
|
1120
|
+
] }) : "\u2014" })
|
|
1121
|
+
] }, entry.key)) }) }) : null
|
|
1122
|
+
] });
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// src/WindowExpiredNotice.tsx
|
|
1126
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1127
|
+
var DEFAULT_WINDOW_EXPIRED_LABELS = {
|
|
1128
|
+
title: "\u23F0 Janela de 24h expirada \u2014 o WhatsApp n\xE3o aceita mensagens livres.",
|
|
1129
|
+
description: "Envie uma mensagem de template para reabrir a conversa com o cliente.",
|
|
1130
|
+
sendTemplate: "\u{1F4E8} Enviar Template (HSM)"
|
|
1131
|
+
};
|
|
1132
|
+
function WindowExpiredNotice({
|
|
1133
|
+
onSendTemplate,
|
|
1134
|
+
disabled = false,
|
|
1135
|
+
labels: labelsOverride,
|
|
1136
|
+
className
|
|
1137
|
+
}) {
|
|
1138
|
+
const labels = { ...DEFAULT_WINDOW_EXPIRED_LABELS, ...labelsOverride };
|
|
1139
|
+
return /* @__PURE__ */ jsxs11("div", { role: "status", className: cn("border-t bg-yellow-50 px-4 py-3 text-sm dark:bg-yellow-950", className), children: [
|
|
1140
|
+
/* @__PURE__ */ jsx13("p", { className: "font-medium text-yellow-900 dark:text-yellow-200", children: labels.title }),
|
|
1141
|
+
/* @__PURE__ */ jsx13("p", { className: "text-yellow-800 dark:text-yellow-300", children: labels.description }),
|
|
1142
|
+
onSendTemplate ? /* @__PURE__ */ jsx13("button", { type: "button", onClick: onSendTemplate, disabled, className: "cv-header-action mt-2", children: labels.sendTemplate }) : null
|
|
1143
|
+
] });
|
|
1144
|
+
}
|
|
1145
|
+
function isWindowBlocking(window2) {
|
|
1146
|
+
return window2 === CONVERSATION_WINDOW.EXPIRED;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// src/ConversationDocumentsPanel.tsx
|
|
1150
|
+
import { useState as useState8 } from "react";
|
|
1241
1151
|
|
|
1242
1152
|
// src/providers/ConversationsProvider.tsx
|
|
1243
|
-
import { createContext as
|
|
1244
|
-
import { jsx as
|
|
1245
|
-
var ConversationsContext =
|
|
1153
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
1154
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1155
|
+
var ConversationsContext = createContext2(null);
|
|
1246
1156
|
function ConversationsProvider({
|
|
1247
1157
|
api,
|
|
1248
1158
|
sse,
|
|
1249
1159
|
children
|
|
1250
1160
|
}) {
|
|
1251
|
-
return /* @__PURE__ */
|
|
1161
|
+
return /* @__PURE__ */ jsx14(ConversationsContext.Provider, { value: { api, sse }, children });
|
|
1252
1162
|
}
|
|
1253
1163
|
function useConversations() {
|
|
1254
|
-
return
|
|
1164
|
+
return useContext2(ConversationsContext);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// src/hooks/useAsyncResource.ts
|
|
1168
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState7 } from "react";
|
|
1169
|
+
function useAsyncResource(fetcher, deps) {
|
|
1170
|
+
const [data, setData] = useState7(void 0);
|
|
1171
|
+
const [loading, setLoading] = useState7(false);
|
|
1172
|
+
const [error, setError] = useState7(void 0);
|
|
1173
|
+
const requestIdRef = useRef3(0);
|
|
1174
|
+
const load = useCallback3(async () => {
|
|
1175
|
+
const requestId = ++requestIdRef.current;
|
|
1176
|
+
setLoading(true);
|
|
1177
|
+
setError(void 0);
|
|
1178
|
+
try {
|
|
1179
|
+
const result = await fetcher();
|
|
1180
|
+
if (requestId === requestIdRef.current) setData(result);
|
|
1181
|
+
} catch (err) {
|
|
1182
|
+
if (requestId === requestIdRef.current) setError(err instanceof Error ? err : new Error(String(err)));
|
|
1183
|
+
} finally {
|
|
1184
|
+
if (requestId === requestIdRef.current) setLoading(false);
|
|
1185
|
+
}
|
|
1186
|
+
}, deps);
|
|
1187
|
+
useEffect4(() => {
|
|
1188
|
+
load();
|
|
1189
|
+
}, [load]);
|
|
1190
|
+
return { data, loading, error, refetch: load };
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// src/hooks/useConversationDocuments.ts
|
|
1194
|
+
function useConversationDocuments(conversationId, params) {
|
|
1195
|
+
const context = useConversations();
|
|
1196
|
+
if (!context) {
|
|
1197
|
+
throw new Error("useConversationDocuments requires an ancestor <ConversationsProvider>");
|
|
1198
|
+
}
|
|
1199
|
+
const { api } = context;
|
|
1200
|
+
const { data, loading, error, refetch } = useAsyncResource(
|
|
1201
|
+
() => conversationId ? api.getDocuments(conversationId, params) : Promise.resolve([]),
|
|
1202
|
+
[conversationId, params?.search, params?.page]
|
|
1203
|
+
);
|
|
1204
|
+
return { documents: data ?? [], loading, error, refetch };
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// src/ConversationDocumentsPanel.tsx
|
|
1208
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1209
|
+
var DEFAULT_CONVERSATION_DOCUMENTS_LABELS = {
|
|
1210
|
+
toggle: "\u{1F4CE} Arquivos",
|
|
1211
|
+
title: "Arquivos da conversa",
|
|
1212
|
+
searchPlaceholder: "Buscar arquivo...",
|
|
1213
|
+
empty: "Nenhum arquivo nesta conversa.",
|
|
1214
|
+
loading: "Carregando arquivos\u2026",
|
|
1215
|
+
failure: "N\xE3o foi poss\xEDvel carregar os arquivos.",
|
|
1216
|
+
download: "Baixar"
|
|
1217
|
+
};
|
|
1218
|
+
function ConversationDocumentsPanel({
|
|
1219
|
+
conversationId,
|
|
1220
|
+
open,
|
|
1221
|
+
labels: labelsOverride,
|
|
1222
|
+
className,
|
|
1223
|
+
classNames
|
|
1224
|
+
}) {
|
|
1225
|
+
const labels = { ...DEFAULT_CONVERSATION_DOCUMENTS_LABELS, ...labelsOverride };
|
|
1226
|
+
const context = useConversations();
|
|
1227
|
+
const [search, setSearch] = useState8("");
|
|
1228
|
+
const { documents, loading, error } = useConversationDocuments(open ? conversationId : void 0, { search });
|
|
1229
|
+
async function handleDownload(uploadId) {
|
|
1230
|
+
const url = await context?.api.getDocumentUrl(uploadId);
|
|
1231
|
+
if (url) window.open(url, "_blank", "noopener,noreferrer");
|
|
1232
|
+
}
|
|
1233
|
+
if (!open) return null;
|
|
1234
|
+
return /* @__PURE__ */ jsx15("div", { className: cn("border-b", classNames?.root, className), children: /* @__PURE__ */ jsxs12("section", { className: cn("px-4 py-3", classNames?.body), children: [
|
|
1235
|
+
/* @__PURE__ */ jsx15("p", { className: "mb-2 text-sm font-medium", children: labels.title }),
|
|
1236
|
+
/* @__PURE__ */ jsx15(
|
|
1237
|
+
"input",
|
|
1238
|
+
{
|
|
1239
|
+
type: "search",
|
|
1240
|
+
value: search,
|
|
1241
|
+
onChange: (event) => setSearch(event.target.value),
|
|
1242
|
+
placeholder: labels.searchPlaceholder,
|
|
1243
|
+
className: "mb-2 w-full rounded-md border px-3 py-2 text-sm"
|
|
1244
|
+
}
|
|
1245
|
+
),
|
|
1246
|
+
loading ? /* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-500", children: labels.loading }) : null,
|
|
1247
|
+
error ? /* @__PURE__ */ jsx15("p", { className: "text-xs text-red-600 dark:text-red-400", children: labels.failure }) : null,
|
|
1248
|
+
!loading && !error && documents.length === 0 ? /* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-500", children: labels.empty }) : null,
|
|
1249
|
+
/* @__PURE__ */ jsx15("ul", { className: "space-y-1", children: documents.map((document2) => /* @__PURE__ */ jsxs12("li", { className: "flex items-center gap-2 text-xs", children: [
|
|
1250
|
+
/* @__PURE__ */ jsx15(FileIcon, { mimeType: document2.mimeType }),
|
|
1251
|
+
/* @__PURE__ */ jsx15("span", { className: "min-w-0 flex-1 truncate", children: document2.filename }),
|
|
1252
|
+
/* @__PURE__ */ jsx15("span", { className: "text-gray-500", children: formatFileSize(document2.sizeBytes) }),
|
|
1253
|
+
/* @__PURE__ */ jsx15("span", { className: "text-gray-500", children: formatTimestamp(document2.linkedAt) }),
|
|
1254
|
+
/* @__PURE__ */ jsx15("button", { type: "button", onClick: () => void handleDownload(document2.id), className: "cv-header-action", children: labels.download })
|
|
1255
|
+
] }, document2.id)) })
|
|
1256
|
+
] }) });
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/conversationTranscript.ts
|
|
1260
|
+
var SENDER_LABEL = {
|
|
1261
|
+
customer: "Cliente",
|
|
1262
|
+
bot: "Bot",
|
|
1263
|
+
agent: "Atendente"
|
|
1264
|
+
};
|
|
1265
|
+
function buildTranscriptText(params) {
|
|
1266
|
+
const header = [
|
|
1267
|
+
`Conversa: ${params.clientName ?? params.whatsappNumber}`,
|
|
1268
|
+
`N\xFAmero: ${params.whatsappNumber}`,
|
|
1269
|
+
`Mensagens: ${params.messages.length}`,
|
|
1270
|
+
""
|
|
1271
|
+
];
|
|
1272
|
+
const lines = params.messages.map((message) => {
|
|
1273
|
+
const stamp = new Date(message.timestamp).toLocaleString("pt-BR");
|
|
1274
|
+
const author = message.agentName ?? SENDER_LABEL[message.sender];
|
|
1275
|
+
const body = message.content ?? message.caption ?? `<${message.type}>`;
|
|
1276
|
+
return `[${stamp}] ${author}: ${body}`;
|
|
1277
|
+
});
|
|
1278
|
+
return [...header, ...lines].join("\n");
|
|
1279
|
+
}
|
|
1280
|
+
function buildTranscriptFilename(whatsappNumber, generatedAt) {
|
|
1281
|
+
const stamp = generatedAt.toISOString().slice(0, 10);
|
|
1282
|
+
return `conversa-${whatsappNumber}-${stamp}.txt`;
|
|
1283
|
+
}
|
|
1284
|
+
function downloadTextFile(filename, content) {
|
|
1285
|
+
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
|
1286
|
+
const url = URL.createObjectURL(blob);
|
|
1287
|
+
const anchor = document.createElement("a");
|
|
1288
|
+
anchor.href = url;
|
|
1289
|
+
anchor.download = filename;
|
|
1290
|
+
anchor.click();
|
|
1291
|
+
URL.revokeObjectURL(url);
|
|
1255
1292
|
}
|
|
1256
1293
|
|
|
1257
1294
|
// src/useWaitingNotifications.ts
|
|
1295
|
+
import { useState as useState9, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback4 } from "react";
|
|
1258
1296
|
function useWaitingNotifications() {
|
|
1259
1297
|
const conversationsCtx = useConversations();
|
|
1260
1298
|
const [conversations, setConversations] = useState9([]);
|
|
1261
|
-
const previousUnreadMap =
|
|
1262
|
-
const notifiedIds =
|
|
1263
|
-
const pollConversations =
|
|
1299
|
+
const previousUnreadMap = useRef4(/* @__PURE__ */ new Map());
|
|
1300
|
+
const notifiedIds = useRef4(/* @__PURE__ */ new Set());
|
|
1301
|
+
const pollConversations = useCallback4(async () => {
|
|
1264
1302
|
if (!conversationsCtx) return;
|
|
1265
1303
|
try {
|
|
1266
1304
|
const result = await conversationsCtx.api.fetchConversations({ limit: 50 });
|
|
@@ -1285,7 +1323,7 @@ function useWaitingNotifications() {
|
|
|
1285
1323
|
} catch {
|
|
1286
1324
|
}
|
|
1287
1325
|
}, [conversationsCtx]);
|
|
1288
|
-
|
|
1326
|
+
useEffect5(() => {
|
|
1289
1327
|
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "default") {
|
|
1290
1328
|
Notification.requestPermission();
|
|
1291
1329
|
}
|
|
@@ -1299,7 +1337,7 @@ function useWaitingNotifications() {
|
|
|
1299
1337
|
|
|
1300
1338
|
// src/settings/WhatsAppTemplateSettingsForm.tsx
|
|
1301
1339
|
import { Plus, RefreshCw, Save, Trash2 } from "lucide-react";
|
|
1302
|
-
import { jsx as
|
|
1340
|
+
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1303
1341
|
var DEFAULT_LABELS = {
|
|
1304
1342
|
sectionTitle: "WhatsApp",
|
|
1305
1343
|
sectionDescription: "Template usado para reabrir a janela de 24h com o cliente.",
|
|
@@ -1358,16 +1396,16 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1358
1396
|
}
|
|
1359
1397
|
onVariablesChange([...variables, token]);
|
|
1360
1398
|
}
|
|
1361
|
-
return /* @__PURE__ */
|
|
1362
|
-
/* @__PURE__ */
|
|
1363
|
-
/* @__PURE__ */
|
|
1364
|
-
/* @__PURE__ */
|
|
1399
|
+
return /* @__PURE__ */ jsxs13("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1400
|
+
/* @__PURE__ */ jsxs13("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1401
|
+
/* @__PURE__ */ jsx16("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }),
|
|
1402
|
+
/* @__PURE__ */ jsx16("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1365
1403
|
] }),
|
|
1366
|
-
/* @__PURE__ */
|
|
1367
|
-
/* @__PURE__ */
|
|
1368
|
-
/* @__PURE__ */
|
|
1369
|
-
/* @__PURE__ */
|
|
1370
|
-
/* @__PURE__ */
|
|
1404
|
+
/* @__PURE__ */ jsxs13("form", { onSubmit: onSave, className: "p-6 space-y-4", children: [
|
|
1405
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1406
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between mb-1.5", children: [
|
|
1407
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.templateLabel }),
|
|
1408
|
+
/* @__PURE__ */ jsxs13(
|
|
1371
1409
|
"button",
|
|
1372
1410
|
{
|
|
1373
1411
|
type: "button",
|
|
@@ -1375,31 +1413,31 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1375
1413
|
disabled: loadingTemplates,
|
|
1376
1414
|
className: "flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-50",
|
|
1377
1415
|
children: [
|
|
1378
|
-
/* @__PURE__ */
|
|
1416
|
+
/* @__PURE__ */ jsx16(RefreshCw, { size: 11, className: loadingTemplates ? "animate-spin" : "" }),
|
|
1379
1417
|
labels.refreshList
|
|
1380
1418
|
]
|
|
1381
1419
|
}
|
|
1382
1420
|
)
|
|
1383
1421
|
] }),
|
|
1384
|
-
templatesError && /* @__PURE__ */
|
|
1422
|
+
templatesError && /* @__PURE__ */ jsxs13("div", { className: "mb-2 rounded-lg bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800 px-3 py-2 text-xs text-amber-700 dark:text-amber-400", children: [
|
|
1385
1423
|
labels.errorLoading,
|
|
1386
|
-
/* @__PURE__ */
|
|
1387
|
-
/* @__PURE__ */
|
|
1424
|
+
/* @__PURE__ */ jsx16("br", {}),
|
|
1425
|
+
/* @__PURE__ */ jsxs13("span", { className: "text-gray-500 dark:text-gray-400 mt-0.5 block", children: [
|
|
1388
1426
|
labels.currentNameSaved,
|
|
1389
1427
|
" ",
|
|
1390
|
-
/* @__PURE__ */
|
|
1428
|
+
/* @__PURE__ */ jsx16("strong", { children: selectedTemplateName || "\u2014" })
|
|
1391
1429
|
] })
|
|
1392
1430
|
] }),
|
|
1393
|
-
loadingTemplates && /* @__PURE__ */
|
|
1394
|
-
!loadingTemplates && !templatesError && /* @__PURE__ */
|
|
1431
|
+
loadingTemplates && /* @__PURE__ */ jsx16("div", { className: "h-10 rounded-xl bg-gray-100 dark:bg-gray-700 animate-pulse" }),
|
|
1432
|
+
!loadingTemplates && !templatesError && /* @__PURE__ */ jsxs13(
|
|
1395
1433
|
"select",
|
|
1396
1434
|
{
|
|
1397
1435
|
value: selectedTemplateName,
|
|
1398
1436
|
onChange: (e) => handleSelect(e.target.value),
|
|
1399
1437
|
className: "w-full border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all",
|
|
1400
1438
|
children: [
|
|
1401
|
-
/* @__PURE__ */
|
|
1402
|
-
templates.map((template) => /* @__PURE__ */
|
|
1439
|
+
/* @__PURE__ */ jsx16("option", { value: "", children: labels.selectPlaceholder }),
|
|
1440
|
+
templates.map((template) => /* @__PURE__ */ jsxs13("option", { value: template.name, disabled: template.status !== "APPROVED", children: [
|
|
1403
1441
|
template.displayName,
|
|
1404
1442
|
" \xB7 ",
|
|
1405
1443
|
template.shortId,
|
|
@@ -1411,27 +1449,27 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1411
1449
|
]
|
|
1412
1450
|
}
|
|
1413
1451
|
),
|
|
1414
|
-
selected?.bodyText && /* @__PURE__ */
|
|
1452
|
+
selected?.bodyText && /* @__PURE__ */ jsx16("div", { className: "mt-2 rounded-lg bg-gray-50 dark:bg-gray-700/50 border border-gray-200 dark:border-gray-600 px-3 py-2 text-xs text-gray-600 dark:text-gray-300 whitespace-pre-wrap", children: selected.bodyText })
|
|
1415
1453
|
] }),
|
|
1416
|
-
/* @__PURE__ */
|
|
1417
|
-
/* @__PURE__ */
|
|
1418
|
-
/* @__PURE__ */
|
|
1419
|
-
/* @__PURE__ */
|
|
1454
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1455
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between mb-1.5", children: [
|
|
1456
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.variablesLabel }),
|
|
1457
|
+
/* @__PURE__ */ jsxs13(
|
|
1420
1458
|
"button",
|
|
1421
1459
|
{
|
|
1422
1460
|
type: "button",
|
|
1423
1461
|
onClick: () => onVariablesChange([...variables, ""]),
|
|
1424
1462
|
className: "flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline",
|
|
1425
1463
|
children: [
|
|
1426
|
-
/* @__PURE__ */
|
|
1464
|
+
/* @__PURE__ */ jsx16(Plus, { size: 12 }),
|
|
1427
1465
|
labels.addVariable
|
|
1428
1466
|
]
|
|
1429
1467
|
}
|
|
1430
1468
|
)
|
|
1431
1469
|
] }),
|
|
1432
|
-
availableVariables.length > 0 && /* @__PURE__ */
|
|
1433
|
-
/* @__PURE__ */
|
|
1434
|
-
/* @__PURE__ */
|
|
1470
|
+
availableVariables.length > 0 && /* @__PURE__ */ jsxs13("div", { className: "mb-3 rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700/40 p-2.5", children: [
|
|
1471
|
+
/* @__PURE__ */ jsx16("p", { className: "text-xs font-medium text-gray-600 dark:text-gray-300 mb-1.5", children: labels.variablesAvailableTitle }),
|
|
1472
|
+
/* @__PURE__ */ jsx16("div", { className: "flex flex-wrap gap-1.5", children: availableVariables.map((available) => /* @__PURE__ */ jsxs13(
|
|
1435
1473
|
"button",
|
|
1436
1474
|
{
|
|
1437
1475
|
type: "button",
|
|
@@ -1439,8 +1477,8 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1439
1477
|
title: `Inserir ${available.token}`,
|
|
1440
1478
|
className: "inline-flex items-center gap-1 rounded-md border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-xs hover:border-blue-400 dark:hover:border-blue-500 transition-colors",
|
|
1441
1479
|
children: [
|
|
1442
|
-
/* @__PURE__ */
|
|
1443
|
-
/* @__PURE__ */
|
|
1480
|
+
/* @__PURE__ */ jsx16("code", { className: "text-blue-600 dark:text-blue-300", children: available.token }),
|
|
1481
|
+
/* @__PURE__ */ jsxs13("span", { className: "text-gray-400 dark:text-gray-500", children: [
|
|
1444
1482
|
"\xB7 ",
|
|
1445
1483
|
available.label
|
|
1446
1484
|
] })
|
|
@@ -1448,13 +1486,13 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1448
1486
|
},
|
|
1449
1487
|
available.token
|
|
1450
1488
|
)) }),
|
|
1451
|
-
/* @__PURE__ */
|
|
1489
|
+
/* @__PURE__ */ jsx16("p", { className: "mt-1.5 text-[11px] text-gray-400 dark:text-gray-500", children: labels.variablesAvailableHint })
|
|
1452
1490
|
] }),
|
|
1453
|
-
/* @__PURE__ */
|
|
1454
|
-
variables.length === 0 && /* @__PURE__ */
|
|
1455
|
-
/* @__PURE__ */
|
|
1456
|
-
/* @__PURE__ */
|
|
1457
|
-
/* @__PURE__ */
|
|
1491
|
+
/* @__PURE__ */ jsx16("p", { className: "text-xs text-gray-400 dark:text-gray-500 mb-2", children: labels.variablesMappingHint }),
|
|
1492
|
+
variables.length === 0 && /* @__PURE__ */ jsx16("p", { className: "text-xs text-gray-400 dark:text-gray-500 italic", children: labels.noVariables }),
|
|
1493
|
+
/* @__PURE__ */ jsx16("div", { className: "space-y-2", children: variables.map((variable, index) => /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", children: [
|
|
1494
|
+
/* @__PURE__ */ jsx16("span", { className: "text-xs text-gray-400 dark:text-gray-500 w-6 text-right flex-shrink-0", children: `{{${index + 1}}}` }),
|
|
1495
|
+
/* @__PURE__ */ jsx16(
|
|
1458
1496
|
"input",
|
|
1459
1497
|
{
|
|
1460
1498
|
type: "text",
|
|
@@ -1464,27 +1502,27 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1464
1502
|
className: "flex-1 border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all placeholder:text-gray-400 dark:placeholder:text-gray-500"
|
|
1465
1503
|
}
|
|
1466
1504
|
),
|
|
1467
|
-
/* @__PURE__ */
|
|
1505
|
+
/* @__PURE__ */ jsx16(
|
|
1468
1506
|
"button",
|
|
1469
1507
|
{
|
|
1470
1508
|
type: "button",
|
|
1471
1509
|
onClick: () => removeVariable(index),
|
|
1472
1510
|
className: "text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors flex-shrink-0",
|
|
1473
1511
|
title: labels.removeVariable,
|
|
1474
|
-
children: /* @__PURE__ */
|
|
1512
|
+
children: /* @__PURE__ */ jsx16(Trash2, { size: 14 })
|
|
1475
1513
|
}
|
|
1476
1514
|
)
|
|
1477
1515
|
] }, index)) })
|
|
1478
1516
|
] }),
|
|
1479
|
-
saveSuccess && /* @__PURE__ */
|
|
1480
|
-
/* @__PURE__ */
|
|
1517
|
+
saveSuccess && /* @__PURE__ */ jsx16("div", { className: "bg-green-50 dark:bg-green-950/40 border border-green-200 dark:border-green-800 rounded-xl px-4 py-3 text-sm text-green-700 dark:text-green-400", children: labels.saveSuccess }),
|
|
1518
|
+
/* @__PURE__ */ jsx16("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs13(
|
|
1481
1519
|
"button",
|
|
1482
1520
|
{
|
|
1483
1521
|
type: "submit",
|
|
1484
1522
|
disabled: saving || !selectedTemplateName.trim(),
|
|
1485
1523
|
className: "flex items-center gap-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm",
|
|
1486
1524
|
children: [
|
|
1487
|
-
saving ? /* @__PURE__ */
|
|
1525
|
+
saving ? /* @__PURE__ */ jsx16("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx16(Save, { size: 14 }),
|
|
1488
1526
|
saving ? labels.saving : labels.save
|
|
1489
1527
|
]
|
|
1490
1528
|
}
|
|
@@ -1495,7 +1533,7 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1495
1533
|
|
|
1496
1534
|
// src/settings/WhatsAppCreateTemplateForm.tsx
|
|
1497
1535
|
import { SendHorizonal } from "lucide-react";
|
|
1498
|
-
import { jsx as
|
|
1536
|
+
import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1499
1537
|
var DEFAULT_LABELS2 = {
|
|
1500
1538
|
sectionTitle: "Criar novo template",
|
|
1501
1539
|
sectionDescription: "Envia um template para aprova\xE7\xE3o da Meta.",
|
|
@@ -1551,16 +1589,16 @@ function WhatsAppCreateTemplateForm({
|
|
|
1551
1589
|
function set(key, next) {
|
|
1552
1590
|
onChange({ ...value, [key]: next });
|
|
1553
1591
|
}
|
|
1554
|
-
return /* @__PURE__ */
|
|
1555
|
-
/* @__PURE__ */
|
|
1556
|
-
/* @__PURE__ */
|
|
1557
|
-
/* @__PURE__ */
|
|
1592
|
+
return /* @__PURE__ */ jsxs14("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1593
|
+
/* @__PURE__ */ jsxs14("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1594
|
+
/* @__PURE__ */ jsx17("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }),
|
|
1595
|
+
/* @__PURE__ */ jsx17("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1558
1596
|
] }),
|
|
1559
|
-
/* @__PURE__ */
|
|
1560
|
-
/* @__PURE__ */
|
|
1561
|
-
/* @__PURE__ */
|
|
1562
|
-
/* @__PURE__ */
|
|
1563
|
-
/* @__PURE__ */
|
|
1597
|
+
/* @__PURE__ */ jsxs14("form", { onSubmit, className: "p-6 space-y-4", children: [
|
|
1598
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1599
|
+
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.nameLabel }),
|
|
1600
|
+
/* @__PURE__ */ jsx17("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.nameHint }),
|
|
1601
|
+
/* @__PURE__ */ jsx17(
|
|
1564
1602
|
"input",
|
|
1565
1603
|
{
|
|
1566
1604
|
type: "text",
|
|
@@ -1572,56 +1610,56 @@ function WhatsAppCreateTemplateForm({
|
|
|
1572
1610
|
}
|
|
1573
1611
|
)
|
|
1574
1612
|
] }),
|
|
1575
|
-
/* @__PURE__ */
|
|
1576
|
-
/* @__PURE__ */
|
|
1577
|
-
/* @__PURE__ */
|
|
1578
|
-
/* @__PURE__ */
|
|
1613
|
+
/* @__PURE__ */ jsxs14("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
1614
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1615
|
+
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.categoryLabel }),
|
|
1616
|
+
/* @__PURE__ */ jsxs14(
|
|
1579
1617
|
"select",
|
|
1580
1618
|
{
|
|
1581
1619
|
value: value.category,
|
|
1582
1620
|
onChange: (e) => set("category", e.target.value),
|
|
1583
1621
|
className: "w-full border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all",
|
|
1584
1622
|
children: [
|
|
1585
|
-
/* @__PURE__ */
|
|
1586
|
-
/* @__PURE__ */
|
|
1623
|
+
/* @__PURE__ */ jsx17("option", { value: "UTILITY", children: "UTILITY (Transacional)" }),
|
|
1624
|
+
/* @__PURE__ */ jsx17("option", { value: "MARKETING", children: "MARKETING (Promocional)" })
|
|
1587
1625
|
]
|
|
1588
1626
|
}
|
|
1589
1627
|
)
|
|
1590
1628
|
] }),
|
|
1591
|
-
/* @__PURE__ */
|
|
1592
|
-
/* @__PURE__ */
|
|
1593
|
-
/* @__PURE__ */
|
|
1629
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1630
|
+
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.languageLabel }),
|
|
1631
|
+
/* @__PURE__ */ jsxs14(
|
|
1594
1632
|
"select",
|
|
1595
1633
|
{
|
|
1596
1634
|
value: value.language,
|
|
1597
1635
|
onChange: (e) => set("language", e.target.value),
|
|
1598
1636
|
className: "w-full border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all",
|
|
1599
1637
|
children: [
|
|
1600
|
-
/* @__PURE__ */
|
|
1601
|
-
/* @__PURE__ */
|
|
1602
|
-
/* @__PURE__ */
|
|
1638
|
+
/* @__PURE__ */ jsx17("option", { value: "pt_BR", children: "Portugu\xEAs (Brasil)" }),
|
|
1639
|
+
/* @__PURE__ */ jsx17("option", { value: "en_US", children: "Ingl\xEAs (EUA)" }),
|
|
1640
|
+
/* @__PURE__ */ jsx17("option", { value: "es_ES", children: "Espanhol" })
|
|
1603
1641
|
]
|
|
1604
1642
|
}
|
|
1605
1643
|
)
|
|
1606
1644
|
] })
|
|
1607
1645
|
] }),
|
|
1608
|
-
/* @__PURE__ */
|
|
1609
|
-
/* @__PURE__ */
|
|
1610
|
-
/* @__PURE__ */
|
|
1611
|
-
/* @__PURE__ */
|
|
1612
|
-
/* @__PURE__ */
|
|
1646
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1647
|
+
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.headerLabel }),
|
|
1648
|
+
/* @__PURE__ */ jsx17("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.headerHint }),
|
|
1649
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex gap-2", children: [
|
|
1650
|
+
/* @__PURE__ */ jsxs14(
|
|
1613
1651
|
"select",
|
|
1614
1652
|
{
|
|
1615
1653
|
value: value.headerType,
|
|
1616
1654
|
onChange: (e) => set("headerType", e.target.value),
|
|
1617
1655
|
className: "border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 transition-all",
|
|
1618
1656
|
children: [
|
|
1619
|
-
/* @__PURE__ */
|
|
1620
|
-
/* @__PURE__ */
|
|
1657
|
+
/* @__PURE__ */ jsx17("option", { value: "NONE", children: labels.headerNone }),
|
|
1658
|
+
/* @__PURE__ */ jsx17("option", { value: "TEXT", children: labels.headerText })
|
|
1621
1659
|
]
|
|
1622
1660
|
}
|
|
1623
1661
|
),
|
|
1624
|
-
value.headerType === "TEXT" && /* @__PURE__ */
|
|
1662
|
+
value.headerType === "TEXT" && /* @__PURE__ */ jsx17(
|
|
1625
1663
|
"input",
|
|
1626
1664
|
{
|
|
1627
1665
|
type: "text",
|
|
@@ -1634,10 +1672,10 @@ function WhatsAppCreateTemplateForm({
|
|
|
1634
1672
|
)
|
|
1635
1673
|
] })
|
|
1636
1674
|
] }),
|
|
1637
|
-
/* @__PURE__ */
|
|
1638
|
-
/* @__PURE__ */
|
|
1639
|
-
/* @__PURE__ */
|
|
1640
|
-
/* @__PURE__ */
|
|
1675
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1676
|
+
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.bodyLabel }),
|
|
1677
|
+
/* @__PURE__ */ jsx17("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.bodyHint }),
|
|
1678
|
+
/* @__PURE__ */ jsx17(
|
|
1641
1679
|
"textarea",
|
|
1642
1680
|
{
|
|
1643
1681
|
value: value.bodyText,
|
|
@@ -1648,26 +1686,26 @@ function WhatsAppCreateTemplateForm({
|
|
|
1648
1686
|
required: true
|
|
1649
1687
|
}
|
|
1650
1688
|
),
|
|
1651
|
-
detectedVariables.length > 0 && /* @__PURE__ */
|
|
1652
|
-
/* @__PURE__ */
|
|
1653
|
-
/* @__PURE__ */
|
|
1654
|
-
/* @__PURE__ */
|
|
1655
|
-
/* @__PURE__ */
|
|
1689
|
+
detectedVariables.length > 0 && /* @__PURE__ */ jsxs14("div", { className: "mt-2 rounded-lg bg-gray-50 dark:bg-gray-700/50 border border-gray-200 dark:border-gray-600 px-3 py-2", children: [
|
|
1690
|
+
/* @__PURE__ */ jsx17("p", { className: "font-medium text-gray-500 dark:text-gray-400 mb-1.5 uppercase tracking-wide text-xs", children: labels.detectedVariables }),
|
|
1691
|
+
/* @__PURE__ */ jsx17("div", { className: "flex flex-wrap gap-2", children: detectedVariables.map((num) => /* @__PURE__ */ jsxs14("span", { className: "inline-flex items-center gap-1 text-xs", children: [
|
|
1692
|
+
/* @__PURE__ */ jsx17("code", { className: "bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300 px-1.5 py-0.5 rounded font-mono", children: `{{${num}}}` }),
|
|
1693
|
+
/* @__PURE__ */ jsxs14("span", { className: "text-gray-400 dark:text-gray-500", children: [
|
|
1656
1694
|
"= ",
|
|
1657
1695
|
variableExamples[Number(num) - 1] ?? labels.variableLabel(Number(num))
|
|
1658
1696
|
] })
|
|
1659
1697
|
] }, num)) }),
|
|
1660
|
-
/* @__PURE__ */
|
|
1698
|
+
/* @__PURE__ */ jsxs14("p", { className: "text-gray-400 dark:text-gray-500 mt-1.5 text-xs", children: [
|
|
1661
1699
|
labels.configureHintPrefix,
|
|
1662
|
-
/* @__PURE__ */
|
|
1700
|
+
/* @__PURE__ */ jsx17("strong", { children: labels.configureHintLink }),
|
|
1663
1701
|
labels.configureHintSuffix
|
|
1664
1702
|
] })
|
|
1665
1703
|
] })
|
|
1666
1704
|
] }),
|
|
1667
|
-
/* @__PURE__ */
|
|
1668
|
-
/* @__PURE__ */
|
|
1669
|
-
/* @__PURE__ */
|
|
1670
|
-
/* @__PURE__ */
|
|
1705
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1706
|
+
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.footerLabel }),
|
|
1707
|
+
/* @__PURE__ */ jsx17("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.footerHint }),
|
|
1708
|
+
/* @__PURE__ */ jsx17(
|
|
1671
1709
|
"input",
|
|
1672
1710
|
{
|
|
1673
1711
|
type: "text",
|
|
@@ -1679,48 +1717,48 @@ function WhatsAppCreateTemplateForm({
|
|
|
1679
1717
|
}
|
|
1680
1718
|
)
|
|
1681
1719
|
] }),
|
|
1682
|
-
/* @__PURE__ */
|
|
1683
|
-
/* @__PURE__ */
|
|
1684
|
-
/* @__PURE__ */
|
|
1685
|
-
/* @__PURE__ */
|
|
1686
|
-
/* @__PURE__ */
|
|
1687
|
-
/* @__PURE__ */
|
|
1688
|
-
/* @__PURE__ */
|
|
1689
|
-
/* @__PURE__ */
|
|
1720
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1721
|
+
/* @__PURE__ */ jsx17("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block", children: labels.previewLabel }),
|
|
1722
|
+
/* @__PURE__ */ jsxs14("div", { className: "rounded-2xl bg-[#e5ddd5] dark:bg-[#1a1a2e] border border-gray-300 dark:border-gray-600 overflow-hidden shadow-inner", children: [
|
|
1723
|
+
/* @__PURE__ */ jsxs14("div", { className: "bg-[#075e54] px-4 py-2.5 flex items-center gap-2", children: [
|
|
1724
|
+
/* @__PURE__ */ jsx17("div", { className: "w-8 h-8 rounded-full bg-white/20 flex items-center justify-center text-white text-xs font-bold", children: previewCompanyName.slice(0, 2).toUpperCase() }),
|
|
1725
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1726
|
+
/* @__PURE__ */ jsx17("p", { className: "text-white text-xs font-semibold leading-tight", children: previewCompanyName }),
|
|
1727
|
+
/* @__PURE__ */ jsx17("p", { className: "text-white/60 text-xs", children: labels.previewOnline })
|
|
1690
1728
|
] })
|
|
1691
1729
|
] }),
|
|
1692
|
-
/* @__PURE__ */
|
|
1693
|
-
/* @__PURE__ */
|
|
1694
|
-
/* @__PURE__ */
|
|
1695
|
-
/* @__PURE__ */
|
|
1730
|
+
/* @__PURE__ */ jsxs14("div", { className: "px-3 py-4 space-y-3", style: { minHeight: "120px" }, children: [
|
|
1731
|
+
/* @__PURE__ */ jsx17("div", { className: "flex justify-start", style: { maxWidth: "85%" }, children: /* @__PURE__ */ jsxs14("div", { className: "bg-white dark:bg-gray-700 rounded-lg rounded-tl-none px-3 py-2 shadow-sm", children: [
|
|
1732
|
+
/* @__PURE__ */ jsx17("p", { className: "text-gray-500 dark:text-gray-400 leading-tight text-sm", children: labels.previewDescription }),
|
|
1733
|
+
/* @__PURE__ */ jsx17("p", { className: "text-gray-400 dark:text-gray-500 mt-1 text-right text-[9px]", children: "12:00" })
|
|
1696
1734
|
] }) }),
|
|
1697
|
-
/* @__PURE__ */
|
|
1698
|
-
value.headerType === "TEXT" && value.headerText && /* @__PURE__ */
|
|
1699
|
-
/* @__PURE__ */
|
|
1700
|
-
value.footerText && /* @__PURE__ */
|
|
1701
|
-
/* @__PURE__ */
|
|
1735
|
+
/* @__PURE__ */ jsx17("div", { className: "flex justify-end ml-auto", style: { maxWidth: "85%" }, children: /* @__PURE__ */ jsxs14("div", { className: "bg-[#dcf8c6] dark:bg-[#1b5e20] rounded-lg rounded-tr-none px-3 py-2 shadow-sm", children: [
|
|
1736
|
+
value.headerType === "TEXT" && value.headerText && /* @__PURE__ */ jsx17("p", { className: "text-xs font-bold text-gray-700 dark:text-gray-200 mb-1", children: fillPreviewTokens(value.headerText) || labels.previewHeaderFallback }),
|
|
1737
|
+
/* @__PURE__ */ jsx17("p", { className: "text-sm text-gray-800 dark:text-gray-100 leading-relaxed", children: value.bodyText ? fillPreviewTokens(value.bodyText) : /* @__PURE__ */ jsx17("span", { className: "text-gray-400 italic", children: labels.previewBodyPlaceholder }) }),
|
|
1738
|
+
value.footerText && /* @__PURE__ */ jsx17("p", { className: "text-gray-400 dark:text-gray-400 mt-1 text-xs", children: value.footerText }),
|
|
1739
|
+
/* @__PURE__ */ jsx17("p", { className: "text-gray-400 dark:text-gray-400 mt-1 text-right text-[9px]", children: "12:01 \u2713" })
|
|
1702
1740
|
] }) })
|
|
1703
1741
|
] })
|
|
1704
1742
|
] })
|
|
1705
1743
|
] }),
|
|
1706
|
-
result && /* @__PURE__ */
|
|
1744
|
+
result && /* @__PURE__ */ jsxs14(
|
|
1707
1745
|
"div",
|
|
1708
1746
|
{
|
|
1709
1747
|
className: `rounded-xl px-4 py-3 text-sm ${result.ok ? "bg-green-50 dark:bg-green-950/40 border border-green-200 dark:border-green-800 text-green-700 dark:text-green-400" : "bg-red-50 dark:bg-red-950/40 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-400"}`,
|
|
1710
1748
|
children: [
|
|
1711
1749
|
result.message,
|
|
1712
|
-
result.ok && result.status && /* @__PURE__ */
|
|
1750
|
+
result.ok && result.status && /* @__PURE__ */ jsx17("p", { className: "text-xs mt-1 opacity-75", children: labels.successStatus(result.status) })
|
|
1713
1751
|
]
|
|
1714
1752
|
}
|
|
1715
1753
|
),
|
|
1716
|
-
/* @__PURE__ */
|
|
1754
|
+
/* @__PURE__ */ jsx17("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs14(
|
|
1717
1755
|
"button",
|
|
1718
1756
|
{
|
|
1719
1757
|
type: "submit",
|
|
1720
1758
|
disabled: submitting || !value.name || !value.bodyText,
|
|
1721
1759
|
className: "flex items-center gap-2 bg-emerald-600 hover:bg-emerald-700 disabled:bg-emerald-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm",
|
|
1722
1760
|
children: [
|
|
1723
|
-
submitting ? /* @__PURE__ */
|
|
1761
|
+
submitting ? /* @__PURE__ */ jsx17("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx17(SendHorizonal, { size: 14 }),
|
|
1724
1762
|
submitting ? labels.sending : labels.sendForApproval
|
|
1725
1763
|
]
|
|
1726
1764
|
}
|
|
@@ -1731,7 +1769,7 @@ function WhatsAppCreateTemplateForm({
|
|
|
1731
1769
|
|
|
1732
1770
|
// src/settings/WelcomeFarewellForm.tsx
|
|
1733
1771
|
import { LogOut, Mail, Save as Save2 } from "lucide-react";
|
|
1734
|
-
import { jsx as
|
|
1772
|
+
import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1735
1773
|
var DEFAULT_LABELS3 = {
|
|
1736
1774
|
sectionTitle: "Boas-vindas e encerramento",
|
|
1737
1775
|
welcomeMessage: "Mensagem de boas-vindas",
|
|
@@ -1759,15 +1797,15 @@ function WelcomeFarewellForm({
|
|
|
1759
1797
|
labels: labelsOverride
|
|
1760
1798
|
}) {
|
|
1761
1799
|
const labels = { ...DEFAULT_LABELS3, ...labelsOverride };
|
|
1762
|
-
return /* @__PURE__ */
|
|
1763
|
-
/* @__PURE__ */
|
|
1764
|
-
/* @__PURE__ */
|
|
1765
|
-
/* @__PURE__ */
|
|
1766
|
-
/* @__PURE__ */
|
|
1767
|
-
/* @__PURE__ */
|
|
1800
|
+
return /* @__PURE__ */ jsxs15("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1801
|
+
/* @__PURE__ */ jsx18("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: /* @__PURE__ */ jsx18("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }) }),
|
|
1802
|
+
/* @__PURE__ */ jsxs15("form", { onSubmit: onSave, className: "p-6 space-y-5", children: [
|
|
1803
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1804
|
+
/* @__PURE__ */ jsxs15("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5 flex items-center gap-1.5", children: [
|
|
1805
|
+
/* @__PURE__ */ jsx18(Mail, { size: 13, className: "text-blue-600 dark:text-blue-400" }),
|
|
1768
1806
|
labels.welcomeMessage
|
|
1769
1807
|
] }),
|
|
1770
|
-
/* @__PURE__ */
|
|
1808
|
+
/* @__PURE__ */ jsx18(
|
|
1771
1809
|
WhatsAppMessageEditor,
|
|
1772
1810
|
{
|
|
1773
1811
|
value: welcomeMessage,
|
|
@@ -1776,14 +1814,14 @@ function WelcomeFarewellForm({
|
|
|
1776
1814
|
placeholders: welcomePlaceholders
|
|
1777
1815
|
}
|
|
1778
1816
|
),
|
|
1779
|
-
/* @__PURE__ */
|
|
1817
|
+
/* @__PURE__ */ jsx18("p", { className: "mt-1.5 text-xs text-gray-500 dark:text-gray-400", children: labels.welcomeMessageHint })
|
|
1780
1818
|
] }),
|
|
1781
|
-
/* @__PURE__ */
|
|
1782
|
-
/* @__PURE__ */
|
|
1783
|
-
/* @__PURE__ */
|
|
1819
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1820
|
+
/* @__PURE__ */ jsxs15("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5 flex items-center gap-1.5", children: [
|
|
1821
|
+
/* @__PURE__ */ jsx18(LogOut, { size: 13, className: "text-blue-600 dark:text-blue-400" }),
|
|
1784
1822
|
labels.farewellMessage
|
|
1785
1823
|
] }),
|
|
1786
|
-
/* @__PURE__ */
|
|
1824
|
+
/* @__PURE__ */ jsx18(
|
|
1787
1825
|
WhatsAppMessageEditor,
|
|
1788
1826
|
{
|
|
1789
1827
|
value: farewellMessage,
|
|
@@ -1792,17 +1830,17 @@ function WelcomeFarewellForm({
|
|
|
1792
1830
|
placeholders: farewellPlaceholders
|
|
1793
1831
|
}
|
|
1794
1832
|
),
|
|
1795
|
-
/* @__PURE__ */
|
|
1833
|
+
/* @__PURE__ */ jsx18("p", { className: "mt-1.5 text-xs text-gray-500 dark:text-gray-400", children: labels.farewellMessageHint })
|
|
1796
1834
|
] }),
|
|
1797
|
-
saveSuccess && /* @__PURE__ */
|
|
1798
|
-
/* @__PURE__ */
|
|
1835
|
+
saveSuccess && /* @__PURE__ */ jsx18("div", { className: "bg-green-50 dark:bg-green-950/40 border border-green-200 dark:border-green-800 rounded-xl px-4 py-3 text-sm text-green-700 dark:text-green-400", children: labels.saveSuccess }),
|
|
1836
|
+
/* @__PURE__ */ jsx18("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs15(
|
|
1799
1837
|
"button",
|
|
1800
1838
|
{
|
|
1801
1839
|
type: "submit",
|
|
1802
1840
|
disabled: saving,
|
|
1803
1841
|
className: "flex items-center gap-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm",
|
|
1804
1842
|
children: [
|
|
1805
|
-
saving ? /* @__PURE__ */
|
|
1843
|
+
saving ? /* @__PURE__ */ jsx18("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx18(Save2, { size: 14 }),
|
|
1806
1844
|
saving ? labels.saving : labels.save
|
|
1807
1845
|
]
|
|
1808
1846
|
}
|
|
@@ -1811,9 +1849,56 @@ function WelcomeFarewellForm({
|
|
|
1811
1849
|
] });
|
|
1812
1850
|
}
|
|
1813
1851
|
|
|
1852
|
+
// src/settings/WhatsAppTemplatesSettings.tsx
|
|
1853
|
+
import { useState as useState10 } from "react";
|
|
1854
|
+
import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1855
|
+
var TEMPLATE_SETTINGS_TAB = {
|
|
1856
|
+
SELECT: "select",
|
|
1857
|
+
CREATE: "create"
|
|
1858
|
+
};
|
|
1859
|
+
var DEFAULT_TEMPLATES_SETTINGS_LABELS = {
|
|
1860
|
+
selectTab: "Template de reengajamento",
|
|
1861
|
+
createTab: "Criar template"
|
|
1862
|
+
};
|
|
1863
|
+
function WhatsAppTemplatesSettings({
|
|
1864
|
+
labels: labelsOverride,
|
|
1865
|
+
create,
|
|
1866
|
+
...settingsProps
|
|
1867
|
+
}) {
|
|
1868
|
+
const labels = { ...DEFAULT_TEMPLATES_SETTINGS_LABELS, ...labelsOverride };
|
|
1869
|
+
const [tab, setTab] = useState10(TEMPLATE_SETTINGS_TAB.SELECT);
|
|
1870
|
+
return /* @__PURE__ */ jsxs16("div", { className: "space-y-4", children: [
|
|
1871
|
+
/* @__PURE__ */ jsxs16("nav", { className: "flex gap-1 border-b", role: "tablist", children: [
|
|
1872
|
+
/* @__PURE__ */ jsx19(
|
|
1873
|
+
"button",
|
|
1874
|
+
{
|
|
1875
|
+
type: "button",
|
|
1876
|
+
role: "tab",
|
|
1877
|
+
"aria-selected": tab === TEMPLATE_SETTINGS_TAB.SELECT,
|
|
1878
|
+
onClick: () => setTab(TEMPLATE_SETTINGS_TAB.SELECT),
|
|
1879
|
+
className: `cv-subtab ${tab === TEMPLATE_SETTINGS_TAB.SELECT ? "cv-subtab--active" : ""}`,
|
|
1880
|
+
children: labels.selectTab
|
|
1881
|
+
}
|
|
1882
|
+
),
|
|
1883
|
+
create ? /* @__PURE__ */ jsx19(
|
|
1884
|
+
"button",
|
|
1885
|
+
{
|
|
1886
|
+
type: "button",
|
|
1887
|
+
role: "tab",
|
|
1888
|
+
"aria-selected": tab === TEMPLATE_SETTINGS_TAB.CREATE,
|
|
1889
|
+
onClick: () => setTab(TEMPLATE_SETTINGS_TAB.CREATE),
|
|
1890
|
+
className: `cv-subtab ${tab === TEMPLATE_SETTINGS_TAB.CREATE ? "cv-subtab--active" : ""}`,
|
|
1891
|
+
children: labels.createTab
|
|
1892
|
+
}
|
|
1893
|
+
) : null
|
|
1894
|
+
] }),
|
|
1895
|
+
tab === TEMPLATE_SETTINGS_TAB.SELECT ? /* @__PURE__ */ jsx19(WhatsAppTemplateSettingsForm, { ...settingsProps }) : create ? /* @__PURE__ */ jsx19(WhatsAppCreateTemplateForm, { ...create }) : null
|
|
1896
|
+
] });
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1814
1899
|
// src/settings/TopicsForm.tsx
|
|
1815
1900
|
import { Megaphone, Save as Save3 } from "lucide-react";
|
|
1816
|
-
import { jsx as
|
|
1901
|
+
import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1817
1902
|
var DEFAULT_LABELS4 = {
|
|
1818
1903
|
sectionTitle: "T\xF3picos intermedi\xE1rios",
|
|
1819
1904
|
sectionDescription: "Mensagens autom\xE1ticas para assuntos espec\xEDficos, disparadas quando o cliente demonstra interesse.",
|
|
@@ -1834,16 +1919,16 @@ function TopicsForm({
|
|
|
1834
1919
|
return /* @__PURE__ */ jsxs17("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1835
1920
|
/* @__PURE__ */ jsxs17("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1836
1921
|
/* @__PURE__ */ jsxs17("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
1837
|
-
/* @__PURE__ */
|
|
1922
|
+
/* @__PURE__ */ jsx20(Megaphone, { size: 16, className: "text-orange-600 dark:text-orange-400" }),
|
|
1838
1923
|
labels.sectionTitle
|
|
1839
1924
|
] }),
|
|
1840
|
-
/* @__PURE__ */
|
|
1925
|
+
/* @__PURE__ */ jsx20("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1841
1926
|
] }),
|
|
1842
1927
|
/* @__PURE__ */ jsxs17("form", { onSubmit: onSave, className: "p-6 space-y-6", children: [
|
|
1843
1928
|
topics.map((topic) => /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
|
|
1844
1929
|
/* @__PURE__ */ jsxs17("div", { className: "flex items-start gap-4", children: [
|
|
1845
|
-
/* @__PURE__ */
|
|
1846
|
-
/* @__PURE__ */
|
|
1930
|
+
/* @__PURE__ */ jsx20("div", { className: "flex-1", children: /* @__PURE__ */ jsx20("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: topic.label }) }),
|
|
1931
|
+
/* @__PURE__ */ jsx20(
|
|
1847
1932
|
"button",
|
|
1848
1933
|
{
|
|
1849
1934
|
type: "button",
|
|
@@ -1851,7 +1936,7 @@ function TopicsForm({
|
|
|
1851
1936
|
className: `relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${topic.enabled ? "bg-blue-600 dark:bg-blue-500" : "bg-gray-200 dark:bg-gray-700"}`,
|
|
1852
1937
|
role: "switch",
|
|
1853
1938
|
"aria-checked": topic.enabled,
|
|
1854
|
-
children: /* @__PURE__ */
|
|
1939
|
+
children: /* @__PURE__ */ jsx20(
|
|
1855
1940
|
"span",
|
|
1856
1941
|
{
|
|
1857
1942
|
className: `pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${topic.enabled ? "translate-x-5" : "translate-x-0"}`
|
|
@@ -1860,7 +1945,7 @@ function TopicsForm({
|
|
|
1860
1945
|
}
|
|
1861
1946
|
)
|
|
1862
1947
|
] }),
|
|
1863
|
-
/* @__PURE__ */
|
|
1948
|
+
/* @__PURE__ */ jsx20(
|
|
1864
1949
|
"textarea",
|
|
1865
1950
|
{
|
|
1866
1951
|
value: topic.message,
|
|
@@ -1880,47 +1965,19 @@ function TopicsForm({
|
|
|
1880
1965
|
disabled: saving,
|
|
1881
1966
|
className: "inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50",
|
|
1882
1967
|
children: [
|
|
1883
|
-
/* @__PURE__ */
|
|
1968
|
+
/* @__PURE__ */ jsx20(Save3, { size: 14 }),
|
|
1884
1969
|
labels.saveButton
|
|
1885
1970
|
]
|
|
1886
1971
|
}
|
|
1887
1972
|
),
|
|
1888
|
-
saveSuccess && /* @__PURE__ */
|
|
1973
|
+
saveSuccess && /* @__PURE__ */ jsx20("span", { className: "text-sm text-green-600 dark:text-green-400", children: labels.saveSuccess })
|
|
1889
1974
|
] })
|
|
1890
1975
|
] })
|
|
1891
1976
|
] });
|
|
1892
1977
|
}
|
|
1893
1978
|
|
|
1894
1979
|
// src/hooks/useConversationMessages.ts
|
|
1895
|
-
import { useCallback as
|
|
1896
|
-
|
|
1897
|
-
// src/hooks/useAsyncResource.ts
|
|
1898
|
-
import { useCallback as useCallback6, useEffect as useEffect5, useRef as useRef6, useState as useState10 } from "react";
|
|
1899
|
-
function useAsyncResource(fetcher, deps) {
|
|
1900
|
-
const [data, setData] = useState10(void 0);
|
|
1901
|
-
const [loading, setLoading] = useState10(false);
|
|
1902
|
-
const [error, setError] = useState10(void 0);
|
|
1903
|
-
const requestIdRef = useRef6(0);
|
|
1904
|
-
const load = useCallback6(async () => {
|
|
1905
|
-
const requestId = ++requestIdRef.current;
|
|
1906
|
-
setLoading(true);
|
|
1907
|
-
setError(void 0);
|
|
1908
|
-
try {
|
|
1909
|
-
const result = await fetcher();
|
|
1910
|
-
if (requestId === requestIdRef.current) setData(result);
|
|
1911
|
-
} catch (err) {
|
|
1912
|
-
if (requestId === requestIdRef.current) setError(err instanceof Error ? err : new Error(String(err)));
|
|
1913
|
-
} finally {
|
|
1914
|
-
if (requestId === requestIdRef.current) setLoading(false);
|
|
1915
|
-
}
|
|
1916
|
-
}, deps);
|
|
1917
|
-
useEffect5(() => {
|
|
1918
|
-
load();
|
|
1919
|
-
}, [load]);
|
|
1920
|
-
return { data, loading, error, refetch: load };
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
// src/hooks/useConversationMessages.ts
|
|
1980
|
+
import { useCallback as useCallback5 } from "react";
|
|
1924
1981
|
function useConversationMessages(conversationId, params) {
|
|
1925
1982
|
const context = useConversations();
|
|
1926
1983
|
if (!context) {
|
|
@@ -1931,7 +1988,7 @@ function useConversationMessages(conversationId, params) {
|
|
|
1931
1988
|
() => api.fetchMessages(conversationId, params),
|
|
1932
1989
|
[conversationId, params?.limit, params?.before]
|
|
1933
1990
|
);
|
|
1934
|
-
const sendMessage =
|
|
1991
|
+
const sendMessage = useCallback5(
|
|
1935
1992
|
async (text) => {
|
|
1936
1993
|
const message = await api.sendMessage(conversationId, text);
|
|
1937
1994
|
await refetch();
|
|
@@ -1939,7 +1996,7 @@ function useConversationMessages(conversationId, params) {
|
|
|
1939
1996
|
},
|
|
1940
1997
|
[api, conversationId, refetch]
|
|
1941
1998
|
);
|
|
1942
|
-
const sendMedia =
|
|
1999
|
+
const sendMedia = useCallback5(
|
|
1943
2000
|
async (mediaData) => {
|
|
1944
2001
|
const message = await api.sendMedia(conversationId, mediaData);
|
|
1945
2002
|
await refetch();
|
|
@@ -1947,14 +2004,14 @@ function useConversationMessages(conversationId, params) {
|
|
|
1947
2004
|
},
|
|
1948
2005
|
[api, conversationId, refetch]
|
|
1949
2006
|
);
|
|
1950
|
-
const sendTemplate =
|
|
2007
|
+
const sendTemplate = useCallback5(
|
|
1951
2008
|
async (templateData) => {
|
|
1952
2009
|
await api.sendTemplate(conversationId, templateData);
|
|
1953
2010
|
await refetch();
|
|
1954
2011
|
},
|
|
1955
2012
|
[api, conversationId, refetch]
|
|
1956
2013
|
);
|
|
1957
|
-
const markRead =
|
|
2014
|
+
const markRead = useCallback5(() => api.markRead(conversationId), [api, conversationId]);
|
|
1958
2015
|
return { messages: data ?? [], loading, error, refetch, sendMessage, sendMedia, sendTemplate, markRead };
|
|
1959
2016
|
}
|
|
1960
2017
|
|
|
@@ -1983,25 +2040,11 @@ function useConversationContext(conversationId) {
|
|
|
1983
2040
|
return { context: data, loading, error, refetch };
|
|
1984
2041
|
}
|
|
1985
2042
|
|
|
1986
|
-
// src/hooks/useConversationDocuments.ts
|
|
1987
|
-
function useConversationDocuments(conversationId, params) {
|
|
1988
|
-
const context = useConversations();
|
|
1989
|
-
if (!context) {
|
|
1990
|
-
throw new Error("useConversationDocuments requires an ancestor <ConversationsProvider>");
|
|
1991
|
-
}
|
|
1992
|
-
const { api } = context;
|
|
1993
|
-
const { data, loading, error, refetch } = useAsyncResource(
|
|
1994
|
-
() => api.getDocuments(conversationId, params),
|
|
1995
|
-
[conversationId, params?.search, params?.page]
|
|
1996
|
-
);
|
|
1997
|
-
return { documents: data ?? [], loading, error, refetch };
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
2043
|
// src/hooks/useConversationRealtime.ts
|
|
2001
|
-
import { useEffect as useEffect6, useRef as
|
|
2044
|
+
import { useEffect as useEffect6, useRef as useRef5 } from "react";
|
|
2002
2045
|
function useConversationRealtime(conversationId, onEvent) {
|
|
2003
2046
|
const context = useConversations();
|
|
2004
|
-
const onEventRef =
|
|
2047
|
+
const onEventRef = useRef5(onEvent);
|
|
2005
2048
|
onEventRef.current = onEvent;
|
|
2006
2049
|
useEffect6(() => {
|
|
2007
2050
|
if (!context || !conversationId) return;
|
|
@@ -2016,7 +2059,7 @@ function useConversationRealtime(conversationId, onEvent) {
|
|
|
2016
2059
|
}
|
|
2017
2060
|
function useGlobalRealtime(onEvent) {
|
|
2018
2061
|
const context = useConversations();
|
|
2019
|
-
const onEventRef =
|
|
2062
|
+
const onEventRef = useRef5(onEvent);
|
|
2020
2063
|
onEventRef.current = onEvent;
|
|
2021
2064
|
useEffect6(() => {
|
|
2022
2065
|
if (!context) return;
|
|
@@ -2032,13 +2075,30 @@ function useGlobalRealtime(onEvent) {
|
|
|
2032
2075
|
export {
|
|
2033
2076
|
AudioPlayer,
|
|
2034
2077
|
Avatar,
|
|
2078
|
+
CHANNEL_BRAND_COLOR,
|
|
2079
|
+
CHANNEL_CAPABILITIES,
|
|
2080
|
+
CHANNEL_FILTER_ALL,
|
|
2081
|
+
CONVERSATION_CHANNEL,
|
|
2082
|
+
CONVERSATION_WINDOW,
|
|
2083
|
+
ChannelIcon,
|
|
2084
|
+
ConversationContextPanel,
|
|
2085
|
+
ConversationDocumentsPanel,
|
|
2086
|
+
ConversationHeader,
|
|
2035
2087
|
ConversationListItem,
|
|
2036
2088
|
ConversationLocalesProvider,
|
|
2089
|
+
ConversationRow,
|
|
2037
2090
|
ConversationWallpaper,
|
|
2038
2091
|
ConversationsProvider,
|
|
2092
|
+
DEFAULT_CONVERSATION_CHANNEL,
|
|
2093
|
+
DEFAULT_CONVERSATION_CONTEXT_LABELS,
|
|
2094
|
+
DEFAULT_CONVERSATION_DOCUMENTS_LABELS,
|
|
2095
|
+
DEFAULT_CONVERSATION_HEADER_LABELS,
|
|
2096
|
+
DEFAULT_TEMPLATES_SETTINGS_LABELS,
|
|
2097
|
+
DEFAULT_WINDOW_EXPIRED_LABELS,
|
|
2039
2098
|
DateDivider,
|
|
2040
2099
|
EmojiPicker,
|
|
2041
2100
|
FileIcon,
|
|
2101
|
+
HANDLE_KIND,
|
|
2042
2102
|
Lightbox,
|
|
2043
2103
|
MediaRenderer,
|
|
2044
2104
|
MessageBubble,
|
|
@@ -2046,18 +2106,33 @@ export {
|
|
|
2046
2106
|
MessageTail,
|
|
2047
2107
|
MessageText,
|
|
2048
2108
|
MessageTimestamp,
|
|
2109
|
+
NARROW_MAX_WIDTH_PX,
|
|
2110
|
+
REOPEN_MECHANISM,
|
|
2049
2111
|
SimpleEmojiPicker,
|
|
2050
2112
|
StatusTicks,
|
|
2113
|
+
TEMPLATE_SETTINGS_TAB,
|
|
2051
2114
|
ToastProvider,
|
|
2052
2115
|
TopicsForm,
|
|
2116
|
+
WINDOW_FILTERS,
|
|
2053
2117
|
WelcomeFarewellForm,
|
|
2054
2118
|
WhatsAppCreateTemplateForm,
|
|
2055
2119
|
WhatsAppMessageEditor,
|
|
2056
2120
|
WhatsAppTemplateSettingsForm,
|
|
2121
|
+
WhatsAppTemplatesSettings,
|
|
2122
|
+
WindowExpiredNotice,
|
|
2123
|
+
buildTranscriptFilename,
|
|
2124
|
+
buildTranscriptText,
|
|
2125
|
+
capabilitiesOf,
|
|
2126
|
+
channelFiltersFor,
|
|
2127
|
+
contactFlag,
|
|
2128
|
+
downloadTextFile,
|
|
2129
|
+
formatContactHandle,
|
|
2057
2130
|
formatFileSize,
|
|
2058
2131
|
formatPhone,
|
|
2132
|
+
formatStalledFor,
|
|
2059
2133
|
formatTimestamp,
|
|
2060
2134
|
htmlToWA,
|
|
2135
|
+
isWindowBlocking,
|
|
2061
2136
|
parseWhatsAppFormatting,
|
|
2062
2137
|
phoneInitials,
|
|
2063
2138
|
toast,
|
|
@@ -2070,8 +2145,11 @@ export {
|
|
|
2070
2145
|
useConversations,
|
|
2071
2146
|
useDarkMode,
|
|
2072
2147
|
useGlobalRealtime,
|
|
2148
|
+
useIsDarkTheme,
|
|
2149
|
+
useIsNarrow,
|
|
2073
2150
|
useToast,
|
|
2074
2151
|
useWaitingNotifications,
|
|
2075
2152
|
waToHTML,
|
|
2076
|
-
waToHTMLInline
|
|
2153
|
+
waToHTMLInline,
|
|
2154
|
+
windowOf
|
|
2077
2155
|
};
|