@adatechnology/conversations-ui 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channel/index.d.ts +2 -0
- package/dist/channel/index.js +0 -0
- package/dist/chunk-ZDURDZTM.js +199 -0
- package/dist/flows/index.d.ts +246 -0
- package/dist/flows/index.js +1110 -0
- package/dist/index.d.ts +542 -0
- package/dist/index.js +2077 -0
- package/dist/styles.css +11 -0
- package/dist/styles.d.ts +2 -0
- package/package.json +52 -0
- package/src/AudioPlayer.tsx +103 -0
- package/src/Avatar.tsx +63 -0
- package/src/ConversationListItem.tsx +147 -0
- package/src/ConversationLocalesProvider.tsx +76 -0
- package/src/DateDivider.tsx +32 -0
- package/src/EmojiPicker.tsx +77 -0
- package/src/FileIcon.tsx +33 -0
- package/src/Lightbox.tsx +17 -0
- package/src/MediaRenderer.tsx +158 -0
- package/src/MessageBubble.tsx +129 -0
- package/src/MessageComposer.tsx +211 -0
- package/src/MessageTail.tsx +18 -0
- package/src/MessageText.tsx +42 -0
- package/src/MessageTimestamp.tsx +22 -0
- package/src/SimpleEmojiPicker.tsx +72 -0
- package/src/StatusTicks.tsx +39 -0
- package/src/Toast.tsx +140 -0
- package/src/Wallpaper.tsx +13 -0
- package/src/WhatsAppMessageEditor.tsx +142 -0
- package/src/channel/index.ts +1 -0
- package/src/conversations/index.ts +1 -0
- package/src/flows/FlowGroupFrame.tsx +21 -0
- package/src/flows/FlowGroupHeader.tsx +31 -0
- package/src/flows/FlowMapCanvas.tsx +76 -0
- package/src/flows/FlowMapNode.tsx +39 -0
- package/src/flows/FlowNodeCard.tsx +150 -0
- package/src/flows/FlowNodePanel.tsx +356 -0
- package/src/flows/FlowPalette.tsx +137 -0
- package/src/flows/FlowPortalNode.tsx +30 -0
- package/src/flows/FlowWhatsAppPreview.tsx +67 -0
- package/src/flows/flowGraph.ts +391 -0
- package/src/flows/index.ts +55 -0
- package/src/flows/labels.ts +187 -0
- package/src/hooks/useAsyncResource.ts +38 -0
- package/src/hooks/useConversationContext.ts +23 -0
- package/src/hooks/useConversationDocuments.ts +33 -0
- package/src/hooks/useConversationList.ts +32 -0
- package/src/hooks/useConversationMessages.ts +64 -0
- package/src/hooks/useConversationRealtime.ts +50 -0
- package/src/index.ts +87 -0
- package/src/lib/format.ts +32 -0
- package/src/lib/phone.ts +26 -0
- package/src/lib/whatsapp-formatting.tsx +215 -0
- package/src/providers/ConversationsProvider.tsx +29 -0
- package/src/providers/types.ts +54 -0
- package/src/settings/TopicsForm.tsx +109 -0
- package/src/settings/WelcomeFarewellForm.tsx +118 -0
- package/src/settings/WhatsAppCreateTemplateForm.tsx +309 -0
- package/src/settings/WhatsAppTemplateSettingsForm.tsx +264 -0
- package/src/styles.css +21 -0
- package/src/theme.ts +30 -0
- package/src/types.ts +44 -0
- package/src/useDarkMode.ts +48 -0
- package/src/useWaitingNotifications.ts +64 -0
- package/tsconfig.json +15 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2077 @@
|
|
|
1
|
+
import {
|
|
2
|
+
htmlToWA,
|
|
3
|
+
parseWhatsAppFormatting,
|
|
4
|
+
useDarkMode,
|
|
5
|
+
waToHTML,
|
|
6
|
+
waToHTMLInline
|
|
7
|
+
} from "./chunk-ZDURDZTM.js";
|
|
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
|
+
};
|
|
645
|
+
|
|
646
|
+
// src/WhatsAppMessageEditor.tsx
|
|
647
|
+
import { useRef as useRef3 } from "react";
|
|
648
|
+
import { Bold, Italic, Strikethrough } from "lucide-react";
|
|
649
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
650
|
+
var INLINE_TOKEN = /(\*[^*\n]+\*|_[^_\n]+_|~[^~\n]+~|`[^`\n]+`|\{[a-zA-Z_]+\})/g;
|
|
651
|
+
function renderLine(line, lineKey) {
|
|
652
|
+
const nodes = [];
|
|
653
|
+
let lastIndex = 0;
|
|
654
|
+
let match;
|
|
655
|
+
let tokenIndex = 0;
|
|
656
|
+
INLINE_TOKEN.lastIndex = 0;
|
|
657
|
+
while ((match = INLINE_TOKEN.exec(line)) !== null) {
|
|
658
|
+
if (match.index > lastIndex) nodes.push(line.slice(lastIndex, match.index));
|
|
659
|
+
const token = match[0];
|
|
660
|
+
const key = `${lineKey}-${tokenIndex++}`;
|
|
661
|
+
if (token.startsWith("*")) nodes.push(/* @__PURE__ */ jsx11("strong", { children: token.slice(1, -1) }, key));
|
|
662
|
+
else if (token.startsWith("_")) nodes.push(/* @__PURE__ */ jsx11("em", { children: token.slice(1, -1) }, key));
|
|
663
|
+
else if (token.startsWith("~")) nodes.push(/* @__PURE__ */ jsx11("s", { children: token.slice(1, -1) }, key));
|
|
664
|
+
else if (token.startsWith("`")) nodes.push(/* @__PURE__ */ jsx11("code", { className: "font-mono text-[0.85em]", children: token.slice(1, -1) }, key));
|
|
665
|
+
else nodes.push(/* @__PURE__ */ jsx11("span", { className: "rounded bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 px-1", children: token }, key));
|
|
666
|
+
lastIndex = INLINE_TOKEN.lastIndex;
|
|
667
|
+
}
|
|
668
|
+
if (lastIndex < line.length) nodes.push(line.slice(lastIndex));
|
|
669
|
+
return nodes;
|
|
670
|
+
}
|
|
671
|
+
function renderWhatsApp(text) {
|
|
672
|
+
const lines = text.split("\n");
|
|
673
|
+
return lines.map((line, index) => /* @__PURE__ */ jsxs8("span", { children: [
|
|
674
|
+
renderLine(line, `ln-${index}`),
|
|
675
|
+
index < lines.length - 1 ? /* @__PURE__ */ jsx11("br", {}) : null
|
|
676
|
+
] }, `ln-${index}`));
|
|
677
|
+
}
|
|
678
|
+
function WhatsAppMessageEditor({
|
|
679
|
+
value,
|
|
680
|
+
onChange,
|
|
681
|
+
placeholder,
|
|
682
|
+
placeholders = [],
|
|
683
|
+
rows = 4,
|
|
684
|
+
previewLabel = "Pr\xE9via (como aparece no WhatsApp)",
|
|
685
|
+
emptyPreviewText = "Sua mensagem aparecer\xE1 aqui\u2026"
|
|
686
|
+
}) {
|
|
687
|
+
const textareaRef = useRef3(null);
|
|
688
|
+
function wrapSelection(marker) {
|
|
689
|
+
const textarea = textareaRef.current;
|
|
690
|
+
if (!textarea) return;
|
|
691
|
+
const start = textarea.selectionStart;
|
|
692
|
+
const end = textarea.selectionEnd;
|
|
693
|
+
const selected = value.slice(start, end) || "texto";
|
|
694
|
+
const next = value.slice(0, start) + marker + selected + marker + value.slice(end);
|
|
695
|
+
onChange(next);
|
|
696
|
+
requestAnimationFrame(() => {
|
|
697
|
+
textarea.focus();
|
|
698
|
+
textarea.setSelectionRange(start + marker.length, start + marker.length + selected.length);
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
function insertAtCursor(snippet) {
|
|
702
|
+
const textarea = textareaRef.current;
|
|
703
|
+
if (!textarea) return;
|
|
704
|
+
const start = textarea.selectionStart;
|
|
705
|
+
const end = textarea.selectionEnd;
|
|
706
|
+
const next = value.slice(0, start) + snippet + value.slice(end);
|
|
707
|
+
onChange(next);
|
|
708
|
+
requestAnimationFrame(() => {
|
|
709
|
+
textarea.focus();
|
|
710
|
+
const caret = start + snippet.length;
|
|
711
|
+
textarea.setSelectionRange(caret, caret);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
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__ */ jsxs8("div", { children: [
|
|
716
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-center gap-1.5 mb-2", children: [
|
|
717
|
+
/* @__PURE__ */ jsx11("button", { type: "button", onClick: () => wrapSelection("*"), className: toolbarButtonClass, title: "Negrito (*texto*)", "aria-label": "Negrito", children: /* @__PURE__ */ jsx11(Bold, { size: 15 }) }),
|
|
718
|
+
/* @__PURE__ */ jsx11("button", { type: "button", onClick: () => wrapSelection("_"), className: toolbarButtonClass, title: "It\xE1lico (_texto_)", "aria-label": "It\xE1lico", children: /* @__PURE__ */ jsx11(Italic, { size: 15 }) }),
|
|
719
|
+
/* @__PURE__ */ jsx11("button", { type: "button", onClick: () => wrapSelection("~"), className: toolbarButtonClass, title: "Tachado (~texto~)", "aria-label": "Tachado", children: /* @__PURE__ */ jsx11(Strikethrough, { size: 15 }) }),
|
|
720
|
+
placeholders.length > 0 && /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
721
|
+
/* @__PURE__ */ jsx11("span", { className: "mx-1 h-5 w-px bg-gray-200 dark:bg-gray-600" }),
|
|
722
|
+
placeholders.map((token) => /* @__PURE__ */ jsx11(
|
|
723
|
+
"button",
|
|
724
|
+
{
|
|
725
|
+
type: "button",
|
|
726
|
+
onClick: () => insertAtCursor(token),
|
|
727
|
+
className: "inline-flex items-center h-8 px-2 rounded-lg border border-gray-200 dark:border-gray-600 text-xs text-blue-600 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/40 transition-colors",
|
|
728
|
+
title: `Inserir ${token}`,
|
|
729
|
+
children: token
|
|
730
|
+
},
|
|
731
|
+
token
|
|
732
|
+
))
|
|
733
|
+
] })
|
|
734
|
+
] }),
|
|
735
|
+
/* @__PURE__ */ jsx11(
|
|
736
|
+
"textarea",
|
|
737
|
+
{
|
|
738
|
+
ref: textareaRef,
|
|
739
|
+
value,
|
|
740
|
+
onChange: (event) => onChange(event.target.value),
|
|
741
|
+
placeholder,
|
|
742
|
+
rows,
|
|
743
|
+
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
|
+
}
|
|
745
|
+
),
|
|
746
|
+
/* @__PURE__ */ jsxs8("div", { className: "mt-2", children: [
|
|
747
|
+
/* @__PURE__ */ jsx11("p", { className: "text-xs text-gray-500 dark:text-gray-400 mb-1", children: previewLabel }),
|
|
748
|
+
/* @__PURE__ */ jsx11("div", { className: "rounded-xl bg-[#e5ded8] dark:bg-gray-900 p-3", children: /* @__PURE__ */ jsx11("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__ */ jsx11("span", { className: "text-gray-400 dark:text-gray-500", children: emptyPreviewText }) }) })
|
|
749
|
+
] })
|
|
750
|
+
] });
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/SimpleEmojiPicker.tsx
|
|
754
|
+
import { useEffect as useEffect2, useRef as useRef4, useState as useState6 } from "react";
|
|
755
|
+
import { Smile } from "lucide-react";
|
|
756
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
757
|
+
var EMOJIS = [
|
|
758
|
+
"\u{1F600}",
|
|
759
|
+
"\u{1F602}",
|
|
760
|
+
"\u{1F923}",
|
|
761
|
+
"\u{1F60A}",
|
|
762
|
+
"\u{1F60D}",
|
|
763
|
+
"\u{1F970}",
|
|
764
|
+
"\u{1F618}",
|
|
765
|
+
"\u{1F61C}",
|
|
766
|
+
"\u{1F92A}",
|
|
767
|
+
"\u{1F60E}",
|
|
768
|
+
"\u{1F929}",
|
|
769
|
+
"\u{1F607}",
|
|
770
|
+
"\u{1F642}",
|
|
771
|
+
"\u{1F60F}",
|
|
772
|
+
"\u{1F60C}",
|
|
773
|
+
"\u{1F614}",
|
|
774
|
+
"\u{1F622}",
|
|
775
|
+
"\u{1F62D}",
|
|
776
|
+
"\u{1F624}",
|
|
777
|
+
"\u{1F621}",
|
|
778
|
+
"\u{1F97A}",
|
|
779
|
+
"\u{1F630}",
|
|
780
|
+
"\u{1F631}",
|
|
781
|
+
"\u{1F973}",
|
|
782
|
+
"\u{1F914}",
|
|
783
|
+
"\u{1F917}",
|
|
784
|
+
"\u{1F44D}",
|
|
785
|
+
"\u{1F44E}",
|
|
786
|
+
"\u{1F44F}",
|
|
787
|
+
"\u{1F64C}",
|
|
788
|
+
"\u{1F91D}",
|
|
789
|
+
"\u{1F4AA}",
|
|
790
|
+
"\u{1F64F}",
|
|
791
|
+
"\u2764\uFE0F",
|
|
792
|
+
"\u{1F9E1}",
|
|
793
|
+
"\u{1F49B}",
|
|
794
|
+
"\u{1F49A}",
|
|
795
|
+
"\u{1F499}",
|
|
796
|
+
"\u{1F49C}",
|
|
797
|
+
"\u{1F5A4}",
|
|
798
|
+
"\u{1F90D}",
|
|
799
|
+
"\u{1F494}",
|
|
800
|
+
"\u{1F525}",
|
|
801
|
+
"\u2B50",
|
|
802
|
+
"\u{1F389}",
|
|
803
|
+
"\u2728",
|
|
804
|
+
"\u{1F4AF}",
|
|
805
|
+
"\u2705",
|
|
806
|
+
"\u274C",
|
|
807
|
+
"\u26A0\uFE0F",
|
|
808
|
+
"\u{1F680}",
|
|
809
|
+
"\u{1F4A1}",
|
|
810
|
+
"\u{1F4CC}",
|
|
811
|
+
"\u{1F4CE}",
|
|
812
|
+
"\u{1F4DD}",
|
|
813
|
+
"\u{1F4CA}",
|
|
814
|
+
"\u{1F4C8}",
|
|
815
|
+
"\u{1F4B0}",
|
|
816
|
+
"\u{1F4B3}",
|
|
817
|
+
"\u{1F3E0}",
|
|
818
|
+
"\u{1F3E6}",
|
|
819
|
+
"\u{1F697}",
|
|
820
|
+
"\u2708\uFE0F",
|
|
821
|
+
"\u23F0",
|
|
822
|
+
"\u{1F4C5}",
|
|
823
|
+
"\u{1F514}",
|
|
824
|
+
"\u{1F4DE}",
|
|
825
|
+
"\u{1F4AC}",
|
|
826
|
+
"\u{1F5E8}\uFE0F",
|
|
827
|
+
"\u{1F44B}",
|
|
828
|
+
"\u{1F916}",
|
|
829
|
+
"\u{1F3AF}",
|
|
830
|
+
"\u{1F3C6}",
|
|
831
|
+
"\u{1F4CB}",
|
|
832
|
+
"\u{1F4C4}",
|
|
833
|
+
"\u{1F50D}",
|
|
834
|
+
"\u{1F393}",
|
|
835
|
+
"\u{1F31F}",
|
|
836
|
+
"\u{1F4BC}",
|
|
837
|
+
"\u{1F6E1}\uFE0F"
|
|
838
|
+
];
|
|
839
|
+
function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px", pickerMaxHeight = "220px" }) {
|
|
840
|
+
const [open, setOpen] = useState6(false);
|
|
841
|
+
const containerRef = useRef4(null);
|
|
842
|
+
useEffect2(() => {
|
|
843
|
+
if (!open) return;
|
|
844
|
+
const handleClickOutside = (e) => {
|
|
845
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
846
|
+
setOpen(false);
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
850
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
851
|
+
}, [open]);
|
|
852
|
+
return /* @__PURE__ */ jsxs9("div", { ref: containerRef, className: "relative flex-shrink-0", children: [
|
|
853
|
+
/* @__PURE__ */ jsx12(
|
|
854
|
+
"button",
|
|
855
|
+
{
|
|
856
|
+
type: "button",
|
|
857
|
+
onClick: () => setOpen((v) => !v),
|
|
858
|
+
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
|
+
title: label,
|
|
860
|
+
children: /* @__PURE__ */ jsx12(Smile, { size: 15 })
|
|
861
|
+
}
|
|
862
|
+
),
|
|
863
|
+
open && /* @__PURE__ */ jsx12("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__ */ jsx12(
|
|
864
|
+
"div",
|
|
865
|
+
{
|
|
866
|
+
className: "grid grid-cols-10 gap-0.5 overflow-y-auto",
|
|
867
|
+
style: { width: pickerWidth, maxHeight: pickerMaxHeight },
|
|
868
|
+
children: EMOJIS.map((emoji) => /* @__PURE__ */ jsx12(
|
|
869
|
+
"button",
|
|
870
|
+
{
|
|
871
|
+
type: "button",
|
|
872
|
+
onClick: () => {
|
|
873
|
+
onSelect(emoji);
|
|
874
|
+
setOpen(false);
|
|
875
|
+
},
|
|
876
|
+
className: "w-7 h-7 flex items-center justify-center rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-lg leading-none transition-colors",
|
|
877
|
+
children: emoji
|
|
878
|
+
},
|
|
879
|
+
emoji
|
|
880
|
+
))
|
|
881
|
+
}
|
|
882
|
+
) })
|
|
883
|
+
] });
|
|
884
|
+
}
|
|
885
|
+
|
|
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
|
+
// src/Avatar.tsx
|
|
903
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
904
|
+
var sizeClasses = {
|
|
905
|
+
sm: "w-6 h-6 text-xs",
|
|
906
|
+
md: "w-8 h-8 text-sm",
|
|
907
|
+
lg: "w-10 h-10 text-base"
|
|
908
|
+
};
|
|
909
|
+
var bgColors = [
|
|
910
|
+
"bg-blue-500",
|
|
911
|
+
"bg-green-500",
|
|
912
|
+
"bg-purple-500",
|
|
913
|
+
"bg-pink-500",
|
|
914
|
+
"bg-yellow-500",
|
|
915
|
+
"bg-red-500",
|
|
916
|
+
"bg-indigo-500"
|
|
917
|
+
];
|
|
918
|
+
function getInitials(name) {
|
|
919
|
+
if (!name) return "?";
|
|
920
|
+
const parts = name.trim().split(" ");
|
|
921
|
+
if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
922
|
+
return (parts[0]?.substring(0, 2) || "?").toUpperCase();
|
|
923
|
+
}
|
|
924
|
+
function getBgColor(name) {
|
|
925
|
+
if (!name) return "bg-gray-400";
|
|
926
|
+
const hash = name.charCodeAt(0) + (name.charCodeAt(name.length - 1) || 0);
|
|
927
|
+
return bgColors[hash % bgColors.length];
|
|
928
|
+
}
|
|
929
|
+
function Avatar({ name, avatarUrl, size = "md", className = "" }) {
|
|
930
|
+
const sizeClass = sizeClasses[size];
|
|
931
|
+
if (avatarUrl) {
|
|
932
|
+
return /* @__PURE__ */ jsx14(
|
|
933
|
+
"img",
|
|
934
|
+
{
|
|
935
|
+
src: avatarUrl,
|
|
936
|
+
alt: name || void 0,
|
|
937
|
+
className: `${sizeClass} rounded-full object-cover flex-shrink-0 ${className}`
|
|
938
|
+
}
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
const initials = getInitials(name);
|
|
942
|
+
const bgColor = getBgColor(name);
|
|
943
|
+
return /* @__PURE__ */ jsx14(
|
|
944
|
+
"div",
|
|
945
|
+
{
|
|
946
|
+
className: `${sizeClass} ${bgColor} rounded-full flex items-center justify-center text-white font-semibold flex-shrink-0 ${className}`,
|
|
947
|
+
title: name || void 0,
|
|
948
|
+
children: initials
|
|
949
|
+
}
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/ConversationListItem.tsx
|
|
954
|
+
import { useMemo } from "react";
|
|
955
|
+
|
|
956
|
+
// src/lib/phone.ts
|
|
957
|
+
function formatPhone(number) {
|
|
958
|
+
const digits = number.replace(/\D/g, "");
|
|
959
|
+
if (digits.length === 13) {
|
|
960
|
+
return `+${digits.slice(0, 2)} (${digits.slice(2, 4)}) ${digits.slice(4, 9)}-${digits.slice(9, 13)}`;
|
|
961
|
+
}
|
|
962
|
+
if (digits.length === 12) {
|
|
963
|
+
return `+${digits.slice(0, 2)} (${digits.slice(2, 4)}) ${digits.slice(4, 8)}-${digits.slice(8, 12)}`;
|
|
964
|
+
}
|
|
965
|
+
if (digits.length === 11) {
|
|
966
|
+
return `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7, 11)}`;
|
|
967
|
+
}
|
|
968
|
+
if (digits.length === 10) {
|
|
969
|
+
return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)}-${digits.slice(6, 10)}`;
|
|
970
|
+
}
|
|
971
|
+
return number;
|
|
972
|
+
}
|
|
973
|
+
function phoneInitials(number) {
|
|
974
|
+
const digits = number.replace(/\D/g, "");
|
|
975
|
+
return digits.slice(-2);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// src/ConversationListItem.tsx
|
|
979
|
+
import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
980
|
+
function formatRelativeTime(dateStr) {
|
|
981
|
+
try {
|
|
982
|
+
const date = new Date(dateStr);
|
|
983
|
+
const now = /* @__PURE__ */ new Date();
|
|
984
|
+
const diffMs = now.getTime() - date.getTime();
|
|
985
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
986
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
987
|
+
const diffDays = Math.floor(diffMs / 864e5);
|
|
988
|
+
if (diffMins < 1) return "Agora";
|
|
989
|
+
if (diffMins < 60) return `${diffMins}m`;
|
|
990
|
+
if (diffHours < 24) return `${diffHours}h`;
|
|
991
|
+
if (diffDays < 7) return `${diffDays}d`;
|
|
992
|
+
const day = date.getDate().toString().padStart(2, "0");
|
|
993
|
+
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
994
|
+
return `${day}/${month}`;
|
|
995
|
+
} catch {
|
|
996
|
+
return "";
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function getWindowStatus(lastInboundAt) {
|
|
1000
|
+
if (!lastInboundAt) return null;
|
|
1001
|
+
try {
|
|
1002
|
+
const lastInbound = new Date(lastInboundAt).getTime();
|
|
1003
|
+
const now = Date.now();
|
|
1004
|
+
const hoursElapsed = (now - lastInbound) / 36e5;
|
|
1005
|
+
if (hoursElapsed <= 12) return { label: "active", color: "#22c55e" };
|
|
1006
|
+
if (hoursElapsed <= 21) return { label: "approaching", color: "#eab308" };
|
|
1007
|
+
if (hoursElapsed <= 24) return { label: "warning", color: "#f97316" };
|
|
1008
|
+
return { label: "expired", color: "#ef4444" };
|
|
1009
|
+
} catch {
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
function lastMessagePreview(conversation) {
|
|
1014
|
+
if (!conversation.lastContent) return { icon: null, preview: "" };
|
|
1015
|
+
const content = conversation.lastContent;
|
|
1016
|
+
if (content.startsWith("[image]") || content.startsWith("[Image]")) {
|
|
1017
|
+
return { icon: "\u{1F5BC}", preview: "Imagem" };
|
|
1018
|
+
}
|
|
1019
|
+
if (content.startsWith("[video]") || content.startsWith("[Video]")) {
|
|
1020
|
+
return { icon: "\u{1F3AC}", preview: "V\xEDdeo" };
|
|
1021
|
+
}
|
|
1022
|
+
if (content.startsWith("[audio]") || content.startsWith("[Audio]")) {
|
|
1023
|
+
return { icon: "\u{1F3A4}", preview: "\xC1udio" };
|
|
1024
|
+
}
|
|
1025
|
+
if (content.startsWith("[document]") || content.startsWith("[Document]")) {
|
|
1026
|
+
return { icon: "\u{1F4C4}", preview: "Documento" };
|
|
1027
|
+
}
|
|
1028
|
+
if (content.startsWith("[sticker]") || content.startsWith("[Sticker]")) {
|
|
1029
|
+
return { icon: "\u{1F3AF}", preview: "Sticker" };
|
|
1030
|
+
}
|
|
1031
|
+
return { icon: null, preview: content };
|
|
1032
|
+
}
|
|
1033
|
+
var ConversationListItem = ({
|
|
1034
|
+
conversation,
|
|
1035
|
+
active = false,
|
|
1036
|
+
selected = false,
|
|
1037
|
+
onClick,
|
|
1038
|
+
onSelect
|
|
1039
|
+
}) => {
|
|
1040
|
+
const isActive = active || selected;
|
|
1041
|
+
const windowStatus = useMemo(() => getWindowStatus(conversation.lastInboundAt), [conversation.lastInboundAt]);
|
|
1042
|
+
const preview = useMemo(() => lastMessagePreview(conversation), [conversation.lastContent]);
|
|
1043
|
+
const name = conversation.clientName || formatPhone(conversation.whatsappNumber) || conversation.whatsappNumber;
|
|
1044
|
+
const timestamp = formatRelativeTime(conversation.lastAt);
|
|
1045
|
+
const isInbound = conversation.lastDirection === "inbound";
|
|
1046
|
+
const handleClick = () => {
|
|
1047
|
+
onClick?.();
|
|
1048
|
+
onSelect?.(conversation.id);
|
|
1049
|
+
};
|
|
1050
|
+
return /* @__PURE__ */ jsxs10(
|
|
1051
|
+
"button",
|
|
1052
|
+
{
|
|
1053
|
+
onClick: handleClick,
|
|
1054
|
+
className: "w-full flex items-center gap-3 px-3 py-2.5 text-left transition-colors hover:bg-[#f0f2f5] border-b border-[#e9edef]",
|
|
1055
|
+
style: {
|
|
1056
|
+
backgroundColor: isActive ? "#f0f2f5" : "transparent"
|
|
1057
|
+
},
|
|
1058
|
+
children: [
|
|
1059
|
+
/* @__PURE__ */ jsxs10("div", { className: "relative flex-shrink-0", children: [
|
|
1060
|
+
/* @__PURE__ */ jsx15(Avatar, { name, size: "lg" }),
|
|
1061
|
+
conversation.waitingHuman && /* @__PURE__ */ jsx15("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
|
+
] }),
|
|
1063
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex-1 min-w-0", children: [
|
|
1064
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1065
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1.5 min-w-0", children: [
|
|
1066
|
+
/* @__PURE__ */ jsx15("span", { className: "text-[16px] text-[#111b21] truncate", children: name }),
|
|
1067
|
+
windowStatus && windowStatus.label === "expired" && /* @__PURE__ */ jsx15("span", { className: "w-2 h-2 rounded-full bg-red-500 flex-shrink-0", title: "Janela expirada" }),
|
|
1068
|
+
windowStatus && windowStatus.label === "warning" && /* @__PURE__ */ jsx15("span", { className: "w-2 h-2 rounded-full bg-orange-500 flex-shrink-0", title: "Janela pr\xF3xima do fim" })
|
|
1069
|
+
] }),
|
|
1070
|
+
timestamp && /* @__PURE__ */ jsx15("span", { className: "text-xs text-[#667781] flex-shrink-0", children: timestamp })
|
|
1071
|
+
] }),
|
|
1072
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between mt-0.5", children: [
|
|
1073
|
+
/* @__PURE__ */ jsxs10("span", { className: "text-sm text-[#667781] truncate max-w-[180px]", children: [
|
|
1074
|
+
preview.icon && /* @__PURE__ */ jsx15("span", { className: "mr-1", children: preview.icon }),
|
|
1075
|
+
preview.preview || "Sem mensagens"
|
|
1076
|
+
] }),
|
|
1077
|
+
conversation.unread > 0 && /* @__PURE__ */ jsx15("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
|
+
] }),
|
|
1079
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-1.5 mt-1", children: [
|
|
1080
|
+
conversation.mode === "human" && /* @__PURE__ */ jsx15("span", { className: "text-[10px] px-1.5 py-0.5 rounded font-medium bg-purple-100 text-purple-700", children: "Humano" }),
|
|
1081
|
+
conversation.mode === "bot" && conversation.waitingHuman && /* @__PURE__ */ jsx15("span", { className: "text-[10px] px-1.5 py-0.5 rounded font-medium bg-amber-100 text-amber-700", children: "Aguardando" })
|
|
1082
|
+
] })
|
|
1083
|
+
] })
|
|
1084
|
+
]
|
|
1085
|
+
}
|
|
1086
|
+
);
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
// src/Toast.tsx
|
|
1090
|
+
import { createContext as createContext2, useContext as useContext2, useState as useState7, useCallback as useCallback3, useEffect as useEffect3 } from "react";
|
|
1091
|
+
import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1092
|
+
var ToastContext = createContext2(null);
|
|
1093
|
+
var nextId = 0;
|
|
1094
|
+
var singletonShow = null;
|
|
1095
|
+
var toast = {
|
|
1096
|
+
show(type, message) {
|
|
1097
|
+
if (singletonShow) {
|
|
1098
|
+
singletonShow(type, message);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
var typeStyles = {
|
|
1103
|
+
success: "bg-green-600",
|
|
1104
|
+
error: "bg-red-600",
|
|
1105
|
+
info: "bg-blue-600"
|
|
1106
|
+
};
|
|
1107
|
+
var typeIcons = {
|
|
1108
|
+
success: /* @__PURE__ */ jsx16("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx16("polyline", { points: "20 6 9 17 4 12" }) }),
|
|
1109
|
+
error: /* @__PURE__ */ jsxs11("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
1110
|
+
/* @__PURE__ */ jsx16("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1111
|
+
/* @__PURE__ */ jsx16("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1112
|
+
] }),
|
|
1113
|
+
info: /* @__PURE__ */ jsxs11("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
1114
|
+
/* @__PURE__ */ jsx16("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1115
|
+
/* @__PURE__ */ jsx16("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
|
|
1116
|
+
/* @__PURE__ */ jsx16("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
|
|
1117
|
+
] })
|
|
1118
|
+
};
|
|
1119
|
+
function ToastItemComponent({ item, onRemove }) {
|
|
1120
|
+
const [exiting, setExiting] = useState7(false);
|
|
1121
|
+
useEffect3(() => {
|
|
1122
|
+
const timer = setTimeout(() => {
|
|
1123
|
+
setExiting(true);
|
|
1124
|
+
setTimeout(() => onRemove(item.id), 300);
|
|
1125
|
+
}, 5e3);
|
|
1126
|
+
return () => clearTimeout(timer);
|
|
1127
|
+
}, [item.id, onRemove]);
|
|
1128
|
+
return /* @__PURE__ */ jsxs11(
|
|
1129
|
+
"div",
|
|
1130
|
+
{
|
|
1131
|
+
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
|
+
children: [
|
|
1133
|
+
/* @__PURE__ */ jsx16("span", { className: "flex-shrink-0", children: typeIcons[item.type] }),
|
|
1134
|
+
/* @__PURE__ */ jsx16("span", { className: "flex-1", children: item.message }),
|
|
1135
|
+
/* @__PURE__ */ jsx16(
|
|
1136
|
+
"button",
|
|
1137
|
+
{
|
|
1138
|
+
onClick: () => {
|
|
1139
|
+
setExiting(true);
|
|
1140
|
+
setTimeout(() => onRemove(item.id), 300);
|
|
1141
|
+
},
|
|
1142
|
+
className: "flex-shrink-0 opacity-70 hover:opacity-100 transition-opacity",
|
|
1143
|
+
children: /* @__PURE__ */ jsxs11("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
1144
|
+
/* @__PURE__ */ jsx16("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1145
|
+
/* @__PURE__ */ jsx16("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1146
|
+
] })
|
|
1147
|
+
}
|
|
1148
|
+
)
|
|
1149
|
+
]
|
|
1150
|
+
}
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
function ToastProvider({ children }) {
|
|
1154
|
+
const [items, setItems] = useState7([]);
|
|
1155
|
+
const show = useCallback3((type, message) => {
|
|
1156
|
+
const id = ++nextId;
|
|
1157
|
+
setItems((prev) => [...prev, { id, type, message }]);
|
|
1158
|
+
}, []);
|
|
1159
|
+
const remove = useCallback3((id) => {
|
|
1160
|
+
setItems((prev) => prev.filter((item) => item.id !== id));
|
|
1161
|
+
}, []);
|
|
1162
|
+
useEffect3(() => {
|
|
1163
|
+
singletonShow = show;
|
|
1164
|
+
return () => {
|
|
1165
|
+
singletonShow = null;
|
|
1166
|
+
};
|
|
1167
|
+
}, [show]);
|
|
1168
|
+
return /* @__PURE__ */ jsxs11(ToastContext.Provider, { value: { show }, children: [
|
|
1169
|
+
children,
|
|
1170
|
+
/* @__PURE__ */ jsx16("div", { className: "fixed top-4 right-4 z-[100] flex flex-col gap-2", children: items.map((item) => /* @__PURE__ */ jsx16(ToastItemComponent, { item, onRemove: remove }, item.id)) }),
|
|
1171
|
+
/* @__PURE__ */ jsx16("style", { children: `
|
|
1172
|
+
@keyframes slide-in {
|
|
1173
|
+
from { transform: translateX(100%); opacity: 0; }
|
|
1174
|
+
to { transform: translateX(0); opacity: 1; }
|
|
1175
|
+
}
|
|
1176
|
+
@keyframes slide-out {
|
|
1177
|
+
from { transform: translateX(0); opacity: 1; }
|
|
1178
|
+
to { transform: translateX(100%); opacity: 0; }
|
|
1179
|
+
}
|
|
1180
|
+
.animate-slide-in { animation: slide-in 0.3s ease-out; }
|
|
1181
|
+
.animate-slide-out { animation: slide-out 0.3s ease-in forwards; }
|
|
1182
|
+
` })
|
|
1183
|
+
] });
|
|
1184
|
+
}
|
|
1185
|
+
function useToast() {
|
|
1186
|
+
const context = useContext2(ToastContext);
|
|
1187
|
+
if (!context) {
|
|
1188
|
+
throw new Error("useToast must be used within a ToastProvider");
|
|
1189
|
+
}
|
|
1190
|
+
return context;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// src/MessageText.tsx
|
|
1194
|
+
import { useCallback as useCallback4, useState as useState8 } from "react";
|
|
1195
|
+
import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1196
|
+
function MessageText({ message }) {
|
|
1197
|
+
const [copied, setCopied] = useState8(false);
|
|
1198
|
+
const handleCopy = useCallback4(async () => {
|
|
1199
|
+
if (!message.content) return;
|
|
1200
|
+
try {
|
|
1201
|
+
await navigator.clipboard.writeText(message.content);
|
|
1202
|
+
setCopied(true);
|
|
1203
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
1204
|
+
} catch {
|
|
1205
|
+
}
|
|
1206
|
+
}, [message.content]);
|
|
1207
|
+
if (message.type === "template")
|
|
1208
|
+
return /* @__PURE__ */ jsx17("p", { className: "text-sm italic text-[#667781]", children: message.content ?? "Template message" });
|
|
1209
|
+
return /* @__PURE__ */ jsxs12(
|
|
1210
|
+
"div",
|
|
1211
|
+
{
|
|
1212
|
+
onClick: handleCopy,
|
|
1213
|
+
className: "text-[14.2px] leading-[19px] whitespace-pre-wrap break-words select-all [&_strong]:font-bold [&_em]:italic [&_del]:line-through",
|
|
1214
|
+
children: [
|
|
1215
|
+
/* @__PURE__ */ jsx17("span", { children: parseWhatsAppFormatting(message.content ?? "") }),
|
|
1216
|
+
copied && /* @__PURE__ */ jsx17("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
|
+
]
|
|
1218
|
+
}
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/MessageTimestamp.tsx
|
|
1223
|
+
import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1224
|
+
function MessageTimestamp({ timestamp, status, isOutbound }) {
|
|
1225
|
+
return /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-end gap-[3px]", children: [
|
|
1226
|
+
/* @__PURE__ */ jsx18("span", { className: "text-[11px] text-[#667781] leading-[15px] select-none", children: timestamp }),
|
|
1227
|
+
isOutbound && status && /* @__PURE__ */ jsx18("span", { className: "flex items-center -mr-[2px]", children: /* @__PURE__ */ jsx18(StatusTicks, { status }) })
|
|
1228
|
+
] });
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// src/MessageTail.tsx
|
|
1232
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1233
|
+
function MessageTail({ isOutbound }) {
|
|
1234
|
+
if (isOutbound)
|
|
1235
|
+
return /* @__PURE__ */ jsx19("svg", { className: "absolute top-0 -right-[5px] w-[5px] h-[11px]", viewBox: "0 0 5 11", fill: "#d9fdd3", children: /* @__PURE__ */ jsx19("path", { d: "M5 0H0v11c1.5-2 4.5-2 5-4V0z" }) });
|
|
1236
|
+
return /* @__PURE__ */ jsx19("svg", { className: "absolute top-0 -left-[5px] w-[5px] h-[11px]", viewBox: "0 0 5 11", fill: "white", children: /* @__PURE__ */ jsx19("path", { d: "M0 0h5v11C3.5 9 .5 9 0 7V0z" }) });
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/useWaitingNotifications.ts
|
|
1240
|
+
import { useState as useState9, useEffect as useEffect4, useRef as useRef5, useCallback as useCallback5 } from "react";
|
|
1241
|
+
|
|
1242
|
+
// src/providers/ConversationsProvider.tsx
|
|
1243
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
1244
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
1245
|
+
var ConversationsContext = createContext3(null);
|
|
1246
|
+
function ConversationsProvider({
|
|
1247
|
+
api,
|
|
1248
|
+
sse,
|
|
1249
|
+
children
|
|
1250
|
+
}) {
|
|
1251
|
+
return /* @__PURE__ */ jsx20(ConversationsContext.Provider, { value: { api, sse }, children });
|
|
1252
|
+
}
|
|
1253
|
+
function useConversations() {
|
|
1254
|
+
return useContext3(ConversationsContext);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// src/useWaitingNotifications.ts
|
|
1258
|
+
function useWaitingNotifications() {
|
|
1259
|
+
const conversationsCtx = useConversations();
|
|
1260
|
+
const [conversations, setConversations] = useState9([]);
|
|
1261
|
+
const previousUnreadMap = useRef5(/* @__PURE__ */ new Map());
|
|
1262
|
+
const notifiedIds = useRef5(/* @__PURE__ */ new Set());
|
|
1263
|
+
const pollConversations = useCallback5(async () => {
|
|
1264
|
+
if (!conversationsCtx) return;
|
|
1265
|
+
try {
|
|
1266
|
+
const result = await conversationsCtx.api.fetchConversations({ limit: 50 });
|
|
1267
|
+
setConversations(result);
|
|
1268
|
+
const currentMap = /* @__PURE__ */ new Map();
|
|
1269
|
+
for (const conv of result) {
|
|
1270
|
+
currentMap.set(conv.id, conv.unread);
|
|
1271
|
+
}
|
|
1272
|
+
for (const conv of result) {
|
|
1273
|
+
if (conv.unread <= 0) continue;
|
|
1274
|
+
const prev = previousUnreadMap.current.get(conv.id) ?? 0;
|
|
1275
|
+
if (conv.unread > prev && !notifiedIds.current.has(conv.id)) {
|
|
1276
|
+
notifiedIds.current.add(conv.id);
|
|
1277
|
+
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "granted") {
|
|
1278
|
+
const title = conv.clientName ?? conv.whatsappNumber;
|
|
1279
|
+
const body = conv.lastContent ?? "Nova mensagem";
|
|
1280
|
+
new Notification(title, { body, icon: "/favicon.ico" });
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
previousUnreadMap.current = currentMap;
|
|
1285
|
+
} catch {
|
|
1286
|
+
}
|
|
1287
|
+
}, [conversationsCtx]);
|
|
1288
|
+
useEffect4(() => {
|
|
1289
|
+
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "default") {
|
|
1290
|
+
Notification.requestPermission();
|
|
1291
|
+
}
|
|
1292
|
+
pollConversations();
|
|
1293
|
+
const interval = setInterval(pollConversations, 1e4);
|
|
1294
|
+
return () => clearInterval(interval);
|
|
1295
|
+
}, [pollConversations]);
|
|
1296
|
+
const unreadCount = conversations.reduce((sum, conv) => sum + conv.unread, 0);
|
|
1297
|
+
return { unreadCount, conversations };
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// src/settings/WhatsAppTemplateSettingsForm.tsx
|
|
1301
|
+
import { Plus, RefreshCw, Save, Trash2 } from "lucide-react";
|
|
1302
|
+
import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1303
|
+
var DEFAULT_LABELS = {
|
|
1304
|
+
sectionTitle: "WhatsApp",
|
|
1305
|
+
sectionDescription: "Template usado para reabrir a janela de 24h com o cliente.",
|
|
1306
|
+
templateLabel: "Template aprovado",
|
|
1307
|
+
refreshList: "Atualizar lista",
|
|
1308
|
+
errorLoading: "N\xE3o foi poss\xEDvel carregar os templates da Meta.",
|
|
1309
|
+
currentNameSaved: "Nome salvo atualmente:",
|
|
1310
|
+
selectPlaceholder: "Selecione um template\u2026",
|
|
1311
|
+
variablesLabel: "Vari\xE1veis do template",
|
|
1312
|
+
addVariable: "Adicionar",
|
|
1313
|
+
removeVariable: "Remover vari\xE1vel",
|
|
1314
|
+
variablesAvailableTitle: "Vari\xE1veis dispon\xEDveis",
|
|
1315
|
+
variablesAvailableHint: "Clique para inserir na pr\xF3xima vari\xE1vel vazia.",
|
|
1316
|
+
variablesMappingHint: "A ordem das vari\xE1veis abaixo deve corresponder \xE0 ordem {{1}}, {{2}}... do template.",
|
|
1317
|
+
noVariables: "Este template n\xE3o tem vari\xE1veis.",
|
|
1318
|
+
save: "Salvar",
|
|
1319
|
+
saving: "Salvando...",
|
|
1320
|
+
saveSuccess: "Configura\xE7\xE3o salva com sucesso."
|
|
1321
|
+
};
|
|
1322
|
+
function WhatsAppTemplateSettingsForm({
|
|
1323
|
+
templates,
|
|
1324
|
+
loadingTemplates = false,
|
|
1325
|
+
templatesError = false,
|
|
1326
|
+
onRefreshTemplates,
|
|
1327
|
+
selectedTemplateName,
|
|
1328
|
+
onSelectTemplate,
|
|
1329
|
+
variables,
|
|
1330
|
+
onVariablesChange,
|
|
1331
|
+
availableVariables = [],
|
|
1332
|
+
saving = false,
|
|
1333
|
+
saveSuccess = false,
|
|
1334
|
+
onSave,
|
|
1335
|
+
labels: labelsOverride
|
|
1336
|
+
}) {
|
|
1337
|
+
const labels = { ...DEFAULT_LABELS, ...labelsOverride };
|
|
1338
|
+
const selected = templates.find((template) => template.name === selectedTemplateName);
|
|
1339
|
+
function handleSelect(name) {
|
|
1340
|
+
const template = templates.find((t) => t.name === name);
|
|
1341
|
+
onSelectTemplate(name, template);
|
|
1342
|
+
}
|
|
1343
|
+
function updateVariable(index, value) {
|
|
1344
|
+
const next = [...variables];
|
|
1345
|
+
next[index] = value;
|
|
1346
|
+
onVariablesChange(next);
|
|
1347
|
+
}
|
|
1348
|
+
function removeVariable(index) {
|
|
1349
|
+
onVariablesChange(variables.filter((_, i) => i !== index));
|
|
1350
|
+
}
|
|
1351
|
+
function insertVariable(token) {
|
|
1352
|
+
const emptyIndex = variables.findIndex((value) => !value.trim());
|
|
1353
|
+
if (emptyIndex >= 0) {
|
|
1354
|
+
const next = [...variables];
|
|
1355
|
+
next[emptyIndex] = token;
|
|
1356
|
+
onVariablesChange(next);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
onVariablesChange([...variables, token]);
|
|
1360
|
+
}
|
|
1361
|
+
return /* @__PURE__ */ jsxs14("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1362
|
+
/* @__PURE__ */ jsxs14("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1363
|
+
/* @__PURE__ */ jsx21("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }),
|
|
1364
|
+
/* @__PURE__ */ jsx21("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1365
|
+
] }),
|
|
1366
|
+
/* @__PURE__ */ jsxs14("form", { onSubmit: onSave, className: "p-6 space-y-4", children: [
|
|
1367
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1368
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between mb-1.5", children: [
|
|
1369
|
+
/* @__PURE__ */ jsx21("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.templateLabel }),
|
|
1370
|
+
/* @__PURE__ */ jsxs14(
|
|
1371
|
+
"button",
|
|
1372
|
+
{
|
|
1373
|
+
type: "button",
|
|
1374
|
+
onClick: onRefreshTemplates,
|
|
1375
|
+
disabled: loadingTemplates,
|
|
1376
|
+
className: "flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-50",
|
|
1377
|
+
children: [
|
|
1378
|
+
/* @__PURE__ */ jsx21(RefreshCw, { size: 11, className: loadingTemplates ? "animate-spin" : "" }),
|
|
1379
|
+
labels.refreshList
|
|
1380
|
+
]
|
|
1381
|
+
}
|
|
1382
|
+
)
|
|
1383
|
+
] }),
|
|
1384
|
+
templatesError && /* @__PURE__ */ jsxs14("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
|
+
labels.errorLoading,
|
|
1386
|
+
/* @__PURE__ */ jsx21("br", {}),
|
|
1387
|
+
/* @__PURE__ */ jsxs14("span", { className: "text-gray-500 dark:text-gray-400 mt-0.5 block", children: [
|
|
1388
|
+
labels.currentNameSaved,
|
|
1389
|
+
" ",
|
|
1390
|
+
/* @__PURE__ */ jsx21("strong", { children: selectedTemplateName || "\u2014" })
|
|
1391
|
+
] })
|
|
1392
|
+
] }),
|
|
1393
|
+
loadingTemplates && /* @__PURE__ */ jsx21("div", { className: "h-10 rounded-xl bg-gray-100 dark:bg-gray-700 animate-pulse" }),
|
|
1394
|
+
!loadingTemplates && !templatesError && /* @__PURE__ */ jsxs14(
|
|
1395
|
+
"select",
|
|
1396
|
+
{
|
|
1397
|
+
value: selectedTemplateName,
|
|
1398
|
+
onChange: (e) => handleSelect(e.target.value),
|
|
1399
|
+
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
|
+
children: [
|
|
1401
|
+
/* @__PURE__ */ jsx21("option", { value: "", children: labels.selectPlaceholder }),
|
|
1402
|
+
templates.map((template) => /* @__PURE__ */ jsxs14("option", { value: template.name, disabled: template.status !== "APPROVED", children: [
|
|
1403
|
+
template.displayName,
|
|
1404
|
+
" \xB7 ",
|
|
1405
|
+
template.shortId,
|
|
1406
|
+
" (",
|
|
1407
|
+
template.language,
|
|
1408
|
+
") ",
|
|
1409
|
+
template.status !== "APPROVED" ? `[${template.status}]` : ""
|
|
1410
|
+
] }, `${template.name}-${template.language}`))
|
|
1411
|
+
]
|
|
1412
|
+
}
|
|
1413
|
+
),
|
|
1414
|
+
selected?.bodyText && /* @__PURE__ */ jsx21("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
|
+
] }),
|
|
1416
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1417
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between mb-1.5", children: [
|
|
1418
|
+
/* @__PURE__ */ jsx21("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.variablesLabel }),
|
|
1419
|
+
/* @__PURE__ */ jsxs14(
|
|
1420
|
+
"button",
|
|
1421
|
+
{
|
|
1422
|
+
type: "button",
|
|
1423
|
+
onClick: () => onVariablesChange([...variables, ""]),
|
|
1424
|
+
className: "flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline",
|
|
1425
|
+
children: [
|
|
1426
|
+
/* @__PURE__ */ jsx21(Plus, { size: 12 }),
|
|
1427
|
+
labels.addVariable
|
|
1428
|
+
]
|
|
1429
|
+
}
|
|
1430
|
+
)
|
|
1431
|
+
] }),
|
|
1432
|
+
availableVariables.length > 0 && /* @__PURE__ */ jsxs14("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: [
|
|
1433
|
+
/* @__PURE__ */ jsx21("p", { className: "text-xs font-medium text-gray-600 dark:text-gray-300 mb-1.5", children: labels.variablesAvailableTitle }),
|
|
1434
|
+
/* @__PURE__ */ jsx21("div", { className: "flex flex-wrap gap-1.5", children: availableVariables.map((available) => /* @__PURE__ */ jsxs14(
|
|
1435
|
+
"button",
|
|
1436
|
+
{
|
|
1437
|
+
type: "button",
|
|
1438
|
+
onClick: () => insertVariable(available.token),
|
|
1439
|
+
title: `Inserir ${available.token}`,
|
|
1440
|
+
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
|
+
children: [
|
|
1442
|
+
/* @__PURE__ */ jsx21("code", { className: "text-blue-600 dark:text-blue-300", children: available.token }),
|
|
1443
|
+
/* @__PURE__ */ jsxs14("span", { className: "text-gray-400 dark:text-gray-500", children: [
|
|
1444
|
+
"\xB7 ",
|
|
1445
|
+
available.label
|
|
1446
|
+
] })
|
|
1447
|
+
]
|
|
1448
|
+
},
|
|
1449
|
+
available.token
|
|
1450
|
+
)) }),
|
|
1451
|
+
/* @__PURE__ */ jsx21("p", { className: "mt-1.5 text-[11px] text-gray-400 dark:text-gray-500", children: labels.variablesAvailableHint })
|
|
1452
|
+
] }),
|
|
1453
|
+
/* @__PURE__ */ jsx21("p", { className: "text-xs text-gray-400 dark:text-gray-500 mb-2", children: labels.variablesMappingHint }),
|
|
1454
|
+
variables.length === 0 && /* @__PURE__ */ jsx21("p", { className: "text-xs text-gray-400 dark:text-gray-500 italic", children: labels.noVariables }),
|
|
1455
|
+
/* @__PURE__ */ jsx21("div", { className: "space-y-2", children: variables.map((variable, index) => /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
|
|
1456
|
+
/* @__PURE__ */ jsx21("span", { className: "text-xs text-gray-400 dark:text-gray-500 w-6 text-right flex-shrink-0", children: `{{${index + 1}}}` }),
|
|
1457
|
+
/* @__PURE__ */ jsx21(
|
|
1458
|
+
"input",
|
|
1459
|
+
{
|
|
1460
|
+
type: "text",
|
|
1461
|
+
value: variable,
|
|
1462
|
+
onChange: (e) => updateVariable(index, e.target.value),
|
|
1463
|
+
placeholder: "Ex: {clientName}",
|
|
1464
|
+
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
|
+
}
|
|
1466
|
+
),
|
|
1467
|
+
/* @__PURE__ */ jsx21(
|
|
1468
|
+
"button",
|
|
1469
|
+
{
|
|
1470
|
+
type: "button",
|
|
1471
|
+
onClick: () => removeVariable(index),
|
|
1472
|
+
className: "text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors flex-shrink-0",
|
|
1473
|
+
title: labels.removeVariable,
|
|
1474
|
+
children: /* @__PURE__ */ jsx21(Trash2, { size: 14 })
|
|
1475
|
+
}
|
|
1476
|
+
)
|
|
1477
|
+
] }, index)) })
|
|
1478
|
+
] }),
|
|
1479
|
+
saveSuccess && /* @__PURE__ */ jsx21("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 }),
|
|
1480
|
+
/* @__PURE__ */ jsx21("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs14(
|
|
1481
|
+
"button",
|
|
1482
|
+
{
|
|
1483
|
+
type: "submit",
|
|
1484
|
+
disabled: saving || !selectedTemplateName.trim(),
|
|
1485
|
+
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
|
+
children: [
|
|
1487
|
+
saving ? /* @__PURE__ */ jsx21("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx21(Save, { size: 14 }),
|
|
1488
|
+
saving ? labels.saving : labels.save
|
|
1489
|
+
]
|
|
1490
|
+
}
|
|
1491
|
+
) })
|
|
1492
|
+
] })
|
|
1493
|
+
] });
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// src/settings/WhatsAppCreateTemplateForm.tsx
|
|
1497
|
+
import { SendHorizonal } from "lucide-react";
|
|
1498
|
+
import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1499
|
+
var DEFAULT_LABELS2 = {
|
|
1500
|
+
sectionTitle: "Criar novo template",
|
|
1501
|
+
sectionDescription: "Envia um template para aprova\xE7\xE3o da Meta.",
|
|
1502
|
+
nameLabel: "Nome do template",
|
|
1503
|
+
nameHint: "Somente letras min\xFAsculas, n\xFAmeros e underscore.",
|
|
1504
|
+
categoryLabel: "Categoria",
|
|
1505
|
+
languageLabel: "Idioma",
|
|
1506
|
+
headerLabel: "Cabe\xE7alho",
|
|
1507
|
+
headerHint: "Opcional \u2014 texto fixo exibido acima do corpo.",
|
|
1508
|
+
headerNone: "Sem cabe\xE7alho",
|
|
1509
|
+
headerText: "Texto",
|
|
1510
|
+
headerPlaceholder: "Ex: Ol\xE1 {{1}}!",
|
|
1511
|
+
bodyLabel: "Corpo da mensagem",
|
|
1512
|
+
bodyHint: "Use {{1}}, {{2}}... para vari\xE1veis din\xE2micas.",
|
|
1513
|
+
bodyPlaceholder: "Ex: Ol\xE1 {{1}}, sua simula\xE7\xE3o para {{2}} est\xE1 pronta.",
|
|
1514
|
+
detectedVariables: "Vari\xE1veis detectadas",
|
|
1515
|
+
variableLabel: (index) => `Vari\xE1vel ${index}`,
|
|
1516
|
+
configureHintPrefix: "Configure o mapeamento em ",
|
|
1517
|
+
configureHintLink: "Template usado para reabrir a janela de 24h",
|
|
1518
|
+
configureHintSuffix: " depois de aprovado.",
|
|
1519
|
+
footerLabel: "Rodap\xE9",
|
|
1520
|
+
footerHint: "Opcional \u2014 texto fixo exibido abaixo do corpo.",
|
|
1521
|
+
footerPlaceholder: "Ex: Empresa LTDA",
|
|
1522
|
+
previewLabel: "Pr\xE9via",
|
|
1523
|
+
previewOnline: "online",
|
|
1524
|
+
previewDescription: "Assim aparecer\xE1 para o cliente no WhatsApp.",
|
|
1525
|
+
previewHeaderFallback: "Cabe\xE7alho",
|
|
1526
|
+
previewBodyPlaceholder: "Corpo da mensagem aparecer\xE1 aqui\u2026",
|
|
1527
|
+
sendForApproval: "Enviar para aprova\xE7\xE3o",
|
|
1528
|
+
sending: "Enviando...",
|
|
1529
|
+
successStatus: (status) => `Status: ${status}`
|
|
1530
|
+
};
|
|
1531
|
+
var DEFAULT_VARIABLE_EXAMPLES = ["Nome do cliente", "Produto", "Valor", "Prazo", "Banco"];
|
|
1532
|
+
function detectVariables(bodyText) {
|
|
1533
|
+
const matches = bodyText.match(/\{\{(\d+)\}\}/g) ?? [];
|
|
1534
|
+
return [...new Set(matches.map((token) => token.replace(/[{}]/g, "")))].sort();
|
|
1535
|
+
}
|
|
1536
|
+
function fillPreviewTokens(text) {
|
|
1537
|
+
return text.replace(/\{\{1\}\}/g, "Jo\xE3o").replace(/\{\{2\}\}/g, "Im\xF3vel").replace(/\{\{3\}\}/g, "R$ 1.500");
|
|
1538
|
+
}
|
|
1539
|
+
function WhatsAppCreateTemplateForm({
|
|
1540
|
+
value,
|
|
1541
|
+
onChange,
|
|
1542
|
+
onSubmit,
|
|
1543
|
+
submitting = false,
|
|
1544
|
+
result,
|
|
1545
|
+
previewCompanyName = "WhatsApp Bot",
|
|
1546
|
+
labels: labelsOverride,
|
|
1547
|
+
variableExamples = DEFAULT_VARIABLE_EXAMPLES
|
|
1548
|
+
}) {
|
|
1549
|
+
const labels = { ...DEFAULT_LABELS2, ...labelsOverride };
|
|
1550
|
+
const detectedVariables = detectVariables(value.bodyText);
|
|
1551
|
+
function set(key, next) {
|
|
1552
|
+
onChange({ ...value, [key]: next });
|
|
1553
|
+
}
|
|
1554
|
+
return /* @__PURE__ */ jsxs15("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1555
|
+
/* @__PURE__ */ jsxs15("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1556
|
+
/* @__PURE__ */ jsx22("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }),
|
|
1557
|
+
/* @__PURE__ */ jsx22("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1558
|
+
] }),
|
|
1559
|
+
/* @__PURE__ */ jsxs15("form", { onSubmit, className: "p-6 space-y-4", children: [
|
|
1560
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1561
|
+
/* @__PURE__ */ jsx22("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.nameLabel }),
|
|
1562
|
+
/* @__PURE__ */ jsx22("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.nameHint }),
|
|
1563
|
+
/* @__PURE__ */ jsx22(
|
|
1564
|
+
"input",
|
|
1565
|
+
{
|
|
1566
|
+
type: "text",
|
|
1567
|
+
value: value.name,
|
|
1568
|
+
onChange: (e) => set("name", e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, "_")),
|
|
1569
|
+
placeholder: "reengajamento_cliente",
|
|
1570
|
+
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",
|
|
1571
|
+
required: true
|
|
1572
|
+
}
|
|
1573
|
+
)
|
|
1574
|
+
] }),
|
|
1575
|
+
/* @__PURE__ */ jsxs15("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
1576
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1577
|
+
/* @__PURE__ */ jsx22("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.categoryLabel }),
|
|
1578
|
+
/* @__PURE__ */ jsxs15(
|
|
1579
|
+
"select",
|
|
1580
|
+
{
|
|
1581
|
+
value: value.category,
|
|
1582
|
+
onChange: (e) => set("category", e.target.value),
|
|
1583
|
+
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
|
+
children: [
|
|
1585
|
+
/* @__PURE__ */ jsx22("option", { value: "UTILITY", children: "UTILITY (Transacional)" }),
|
|
1586
|
+
/* @__PURE__ */ jsx22("option", { value: "MARKETING", children: "MARKETING (Promocional)" })
|
|
1587
|
+
]
|
|
1588
|
+
}
|
|
1589
|
+
)
|
|
1590
|
+
] }),
|
|
1591
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1592
|
+
/* @__PURE__ */ jsx22("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.languageLabel }),
|
|
1593
|
+
/* @__PURE__ */ jsxs15(
|
|
1594
|
+
"select",
|
|
1595
|
+
{
|
|
1596
|
+
value: value.language,
|
|
1597
|
+
onChange: (e) => set("language", e.target.value),
|
|
1598
|
+
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
|
+
children: [
|
|
1600
|
+
/* @__PURE__ */ jsx22("option", { value: "pt_BR", children: "Portugu\xEAs (Brasil)" }),
|
|
1601
|
+
/* @__PURE__ */ jsx22("option", { value: "en_US", children: "Ingl\xEAs (EUA)" }),
|
|
1602
|
+
/* @__PURE__ */ jsx22("option", { value: "es_ES", children: "Espanhol" })
|
|
1603
|
+
]
|
|
1604
|
+
}
|
|
1605
|
+
)
|
|
1606
|
+
] })
|
|
1607
|
+
] }),
|
|
1608
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1609
|
+
/* @__PURE__ */ jsx22("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.headerLabel }),
|
|
1610
|
+
/* @__PURE__ */ jsx22("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.headerHint }),
|
|
1611
|
+
/* @__PURE__ */ jsxs15("div", { className: "flex gap-2", children: [
|
|
1612
|
+
/* @__PURE__ */ jsxs15(
|
|
1613
|
+
"select",
|
|
1614
|
+
{
|
|
1615
|
+
value: value.headerType,
|
|
1616
|
+
onChange: (e) => set("headerType", e.target.value),
|
|
1617
|
+
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
|
+
children: [
|
|
1619
|
+
/* @__PURE__ */ jsx22("option", { value: "NONE", children: labels.headerNone }),
|
|
1620
|
+
/* @__PURE__ */ jsx22("option", { value: "TEXT", children: labels.headerText })
|
|
1621
|
+
]
|
|
1622
|
+
}
|
|
1623
|
+
),
|
|
1624
|
+
value.headerType === "TEXT" && /* @__PURE__ */ jsx22(
|
|
1625
|
+
"input",
|
|
1626
|
+
{
|
|
1627
|
+
type: "text",
|
|
1628
|
+
value: value.headerText,
|
|
1629
|
+
onChange: (e) => set("headerText", e.target.value),
|
|
1630
|
+
placeholder: labels.headerPlaceholder,
|
|
1631
|
+
maxLength: 60,
|
|
1632
|
+
className: "flex-1 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"
|
|
1633
|
+
}
|
|
1634
|
+
)
|
|
1635
|
+
] })
|
|
1636
|
+
] }),
|
|
1637
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1638
|
+
/* @__PURE__ */ jsx22("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.bodyLabel }),
|
|
1639
|
+
/* @__PURE__ */ jsx22("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.bodyHint }),
|
|
1640
|
+
/* @__PURE__ */ jsx22(
|
|
1641
|
+
"textarea",
|
|
1642
|
+
{
|
|
1643
|
+
value: value.bodyText,
|
|
1644
|
+
onChange: (e) => set("bodyText", e.target.value),
|
|
1645
|
+
placeholder: labels.bodyPlaceholder,
|
|
1646
|
+
rows: 4,
|
|
1647
|
+
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 resize-none placeholder:text-gray-400 dark:placeholder:text-gray-500",
|
|
1648
|
+
required: true
|
|
1649
|
+
}
|
|
1650
|
+
),
|
|
1651
|
+
detectedVariables.length > 0 && /* @__PURE__ */ jsxs15("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: [
|
|
1652
|
+
/* @__PURE__ */ jsx22("p", { className: "font-medium text-gray-500 dark:text-gray-400 mb-1.5 uppercase tracking-wide text-xs", children: labels.detectedVariables }),
|
|
1653
|
+
/* @__PURE__ */ jsx22("div", { className: "flex flex-wrap gap-2", children: detectedVariables.map((num) => /* @__PURE__ */ jsxs15("span", { className: "inline-flex items-center gap-1 text-xs", children: [
|
|
1654
|
+
/* @__PURE__ */ jsx22("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}}}` }),
|
|
1655
|
+
/* @__PURE__ */ jsxs15("span", { className: "text-gray-400 dark:text-gray-500", children: [
|
|
1656
|
+
"= ",
|
|
1657
|
+
variableExamples[Number(num) - 1] ?? labels.variableLabel(Number(num))
|
|
1658
|
+
] })
|
|
1659
|
+
] }, num)) }),
|
|
1660
|
+
/* @__PURE__ */ jsxs15("p", { className: "text-gray-400 dark:text-gray-500 mt-1.5 text-xs", children: [
|
|
1661
|
+
labels.configureHintPrefix,
|
|
1662
|
+
/* @__PURE__ */ jsx22("strong", { children: labels.configureHintLink }),
|
|
1663
|
+
labels.configureHintSuffix
|
|
1664
|
+
] })
|
|
1665
|
+
] })
|
|
1666
|
+
] }),
|
|
1667
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1668
|
+
/* @__PURE__ */ jsx22("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.footerLabel }),
|
|
1669
|
+
/* @__PURE__ */ jsx22("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.footerHint }),
|
|
1670
|
+
/* @__PURE__ */ jsx22(
|
|
1671
|
+
"input",
|
|
1672
|
+
{
|
|
1673
|
+
type: "text",
|
|
1674
|
+
value: value.footerText,
|
|
1675
|
+
onChange: (e) => set("footerText", e.target.value),
|
|
1676
|
+
placeholder: labels.footerPlaceholder,
|
|
1677
|
+
maxLength: 60,
|
|
1678
|
+
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"
|
|
1679
|
+
}
|
|
1680
|
+
)
|
|
1681
|
+
] }),
|
|
1682
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1683
|
+
/* @__PURE__ */ jsx22("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block", children: labels.previewLabel }),
|
|
1684
|
+
/* @__PURE__ */ jsxs15("div", { className: "rounded-2xl bg-[#e5ddd5] dark:bg-[#1a1a2e] border border-gray-300 dark:border-gray-600 overflow-hidden shadow-inner", children: [
|
|
1685
|
+
/* @__PURE__ */ jsxs15("div", { className: "bg-[#075e54] px-4 py-2.5 flex items-center gap-2", children: [
|
|
1686
|
+
/* @__PURE__ */ jsx22("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() }),
|
|
1687
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1688
|
+
/* @__PURE__ */ jsx22("p", { className: "text-white text-xs font-semibold leading-tight", children: previewCompanyName }),
|
|
1689
|
+
/* @__PURE__ */ jsx22("p", { className: "text-white/60 text-xs", children: labels.previewOnline })
|
|
1690
|
+
] })
|
|
1691
|
+
] }),
|
|
1692
|
+
/* @__PURE__ */ jsxs15("div", { className: "px-3 py-4 space-y-3", style: { minHeight: "120px" }, children: [
|
|
1693
|
+
/* @__PURE__ */ jsx22("div", { className: "flex justify-start", style: { maxWidth: "85%" }, children: /* @__PURE__ */ jsxs15("div", { className: "bg-white dark:bg-gray-700 rounded-lg rounded-tl-none px-3 py-2 shadow-sm", children: [
|
|
1694
|
+
/* @__PURE__ */ jsx22("p", { className: "text-gray-500 dark:text-gray-400 leading-tight text-sm", children: labels.previewDescription }),
|
|
1695
|
+
/* @__PURE__ */ jsx22("p", { className: "text-gray-400 dark:text-gray-500 mt-1 text-right text-[9px]", children: "12:00" })
|
|
1696
|
+
] }) }),
|
|
1697
|
+
/* @__PURE__ */ jsx22("div", { className: "flex justify-end ml-auto", style: { maxWidth: "85%" }, children: /* @__PURE__ */ jsxs15("div", { className: "bg-[#dcf8c6] dark:bg-[#1b5e20] rounded-lg rounded-tr-none px-3 py-2 shadow-sm", children: [
|
|
1698
|
+
value.headerType === "TEXT" && value.headerText && /* @__PURE__ */ jsx22("p", { className: "text-xs font-bold text-gray-700 dark:text-gray-200 mb-1", children: fillPreviewTokens(value.headerText) || labels.previewHeaderFallback }),
|
|
1699
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm text-gray-800 dark:text-gray-100 leading-relaxed", children: value.bodyText ? fillPreviewTokens(value.bodyText) : /* @__PURE__ */ jsx22("span", { className: "text-gray-400 italic", children: labels.previewBodyPlaceholder }) }),
|
|
1700
|
+
value.footerText && /* @__PURE__ */ jsx22("p", { className: "text-gray-400 dark:text-gray-400 mt-1 text-xs", children: value.footerText }),
|
|
1701
|
+
/* @__PURE__ */ jsx22("p", { className: "text-gray-400 dark:text-gray-400 mt-1 text-right text-[9px]", children: "12:01 \u2713" })
|
|
1702
|
+
] }) })
|
|
1703
|
+
] })
|
|
1704
|
+
] })
|
|
1705
|
+
] }),
|
|
1706
|
+
result && /* @__PURE__ */ jsxs15(
|
|
1707
|
+
"div",
|
|
1708
|
+
{
|
|
1709
|
+
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
|
+
children: [
|
|
1711
|
+
result.message,
|
|
1712
|
+
result.ok && result.status && /* @__PURE__ */ jsx22("p", { className: "text-xs mt-1 opacity-75", children: labels.successStatus(result.status) })
|
|
1713
|
+
]
|
|
1714
|
+
}
|
|
1715
|
+
),
|
|
1716
|
+
/* @__PURE__ */ jsx22("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs15(
|
|
1717
|
+
"button",
|
|
1718
|
+
{
|
|
1719
|
+
type: "submit",
|
|
1720
|
+
disabled: submitting || !value.name || !value.bodyText,
|
|
1721
|
+
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
|
+
children: [
|
|
1723
|
+
submitting ? /* @__PURE__ */ jsx22("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx22(SendHorizonal, { size: 14 }),
|
|
1724
|
+
submitting ? labels.sending : labels.sendForApproval
|
|
1725
|
+
]
|
|
1726
|
+
}
|
|
1727
|
+
) })
|
|
1728
|
+
] })
|
|
1729
|
+
] });
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// src/settings/WelcomeFarewellForm.tsx
|
|
1733
|
+
import { LogOut, Mail, Save as Save2 } from "lucide-react";
|
|
1734
|
+
import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1735
|
+
var DEFAULT_LABELS3 = {
|
|
1736
|
+
sectionTitle: "Boas-vindas e encerramento",
|
|
1737
|
+
welcomeMessage: "Mensagem de boas-vindas",
|
|
1738
|
+
welcomeMessagePlaceholder: "Ol\xE1! Sou o assistente virtual da {empresa}...",
|
|
1739
|
+
welcomeMessageHint: "Enviada automaticamente no primeiro contato do cliente.",
|
|
1740
|
+
farewellMessage: "Mensagem de encerramento",
|
|
1741
|
+
farewellMessagePlaceholder: "Obrigado pelo contato! Se precisar de algo, \xE9 s\xF3 chamar.",
|
|
1742
|
+
farewellMessageHint: "Enviada quando o atendente encerra a conversa.",
|
|
1743
|
+
save: "Salvar",
|
|
1744
|
+
saving: "Salvando...",
|
|
1745
|
+
saveSuccess: "Mensagens salvas com sucesso."
|
|
1746
|
+
};
|
|
1747
|
+
var DEFAULT_WELCOME_PLACEHOLDERS = ["{empresa}", "{telefone}", "{email}", "{endereco}", "{maps}"];
|
|
1748
|
+
var DEFAULT_FAREWELL_PLACEHOLDERS = ["{empresa}", "{telefone}", "{email}"];
|
|
1749
|
+
function WelcomeFarewellForm({
|
|
1750
|
+
welcomeMessage,
|
|
1751
|
+
onWelcomeMessageChange,
|
|
1752
|
+
farewellMessage,
|
|
1753
|
+
onFarewellMessageChange,
|
|
1754
|
+
onSave,
|
|
1755
|
+
saving = false,
|
|
1756
|
+
saveSuccess = false,
|
|
1757
|
+
welcomePlaceholders = DEFAULT_WELCOME_PLACEHOLDERS,
|
|
1758
|
+
farewellPlaceholders = DEFAULT_FAREWELL_PLACEHOLDERS,
|
|
1759
|
+
labels: labelsOverride
|
|
1760
|
+
}) {
|
|
1761
|
+
const labels = { ...DEFAULT_LABELS3, ...labelsOverride };
|
|
1762
|
+
return /* @__PURE__ */ jsxs16("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1763
|
+
/* @__PURE__ */ jsx23("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: /* @__PURE__ */ jsx23("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }) }),
|
|
1764
|
+
/* @__PURE__ */ jsxs16("form", { onSubmit: onSave, className: "p-6 space-y-5", children: [
|
|
1765
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1766
|
+
/* @__PURE__ */ jsxs16("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5 flex items-center gap-1.5", children: [
|
|
1767
|
+
/* @__PURE__ */ jsx23(Mail, { size: 13, className: "text-blue-600 dark:text-blue-400" }),
|
|
1768
|
+
labels.welcomeMessage
|
|
1769
|
+
] }),
|
|
1770
|
+
/* @__PURE__ */ jsx23(
|
|
1771
|
+
WhatsAppMessageEditor,
|
|
1772
|
+
{
|
|
1773
|
+
value: welcomeMessage,
|
|
1774
|
+
onChange: onWelcomeMessageChange,
|
|
1775
|
+
placeholder: labels.welcomeMessagePlaceholder,
|
|
1776
|
+
placeholders: welcomePlaceholders
|
|
1777
|
+
}
|
|
1778
|
+
),
|
|
1779
|
+
/* @__PURE__ */ jsx23("p", { className: "mt-1.5 text-xs text-gray-500 dark:text-gray-400", children: labels.welcomeMessageHint })
|
|
1780
|
+
] }),
|
|
1781
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1782
|
+
/* @__PURE__ */ jsxs16("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5 flex items-center gap-1.5", children: [
|
|
1783
|
+
/* @__PURE__ */ jsx23(LogOut, { size: 13, className: "text-blue-600 dark:text-blue-400" }),
|
|
1784
|
+
labels.farewellMessage
|
|
1785
|
+
] }),
|
|
1786
|
+
/* @__PURE__ */ jsx23(
|
|
1787
|
+
WhatsAppMessageEditor,
|
|
1788
|
+
{
|
|
1789
|
+
value: farewellMessage,
|
|
1790
|
+
onChange: onFarewellMessageChange,
|
|
1791
|
+
placeholder: labels.farewellMessagePlaceholder,
|
|
1792
|
+
placeholders: farewellPlaceholders
|
|
1793
|
+
}
|
|
1794
|
+
),
|
|
1795
|
+
/* @__PURE__ */ jsx23("p", { className: "mt-1.5 text-xs text-gray-500 dark:text-gray-400", children: labels.farewellMessageHint })
|
|
1796
|
+
] }),
|
|
1797
|
+
saveSuccess && /* @__PURE__ */ jsx23("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 }),
|
|
1798
|
+
/* @__PURE__ */ jsx23("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs16(
|
|
1799
|
+
"button",
|
|
1800
|
+
{
|
|
1801
|
+
type: "submit",
|
|
1802
|
+
disabled: saving,
|
|
1803
|
+
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
|
+
children: [
|
|
1805
|
+
saving ? /* @__PURE__ */ jsx23("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx23(Save2, { size: 14 }),
|
|
1806
|
+
saving ? labels.saving : labels.save
|
|
1807
|
+
]
|
|
1808
|
+
}
|
|
1809
|
+
) })
|
|
1810
|
+
] })
|
|
1811
|
+
] });
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
// src/settings/TopicsForm.tsx
|
|
1815
|
+
import { Megaphone, Save as Save3 } from "lucide-react";
|
|
1816
|
+
import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1817
|
+
var DEFAULT_LABELS4 = {
|
|
1818
|
+
sectionTitle: "T\xF3picos intermedi\xE1rios",
|
|
1819
|
+
sectionDescription: "Mensagens autom\xE1ticas para assuntos espec\xEDficos, disparadas quando o cliente demonstra interesse.",
|
|
1820
|
+
messagePlaceholder: "Digite a mensagem autom\xE1tica para este t\xF3pico...",
|
|
1821
|
+
saveButton: "Salvar t\xF3picos",
|
|
1822
|
+
saveSuccess: "T\xF3picos salvos com sucesso."
|
|
1823
|
+
};
|
|
1824
|
+
function TopicsForm({
|
|
1825
|
+
topics,
|
|
1826
|
+
onToggle,
|
|
1827
|
+
onMessageChange,
|
|
1828
|
+
onSave,
|
|
1829
|
+
saving = false,
|
|
1830
|
+
saveSuccess = false,
|
|
1831
|
+
labels: labelsOverride
|
|
1832
|
+
}) {
|
|
1833
|
+
const labels = { ...DEFAULT_LABELS4, ...labelsOverride };
|
|
1834
|
+
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
|
+
/* @__PURE__ */ jsxs17("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1836
|
+
/* @__PURE__ */ jsxs17("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
1837
|
+
/* @__PURE__ */ jsx24(Megaphone, { size: 16, className: "text-orange-600 dark:text-orange-400" }),
|
|
1838
|
+
labels.sectionTitle
|
|
1839
|
+
] }),
|
|
1840
|
+
/* @__PURE__ */ jsx24("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1841
|
+
] }),
|
|
1842
|
+
/* @__PURE__ */ jsxs17("form", { onSubmit: onSave, className: "p-6 space-y-6", children: [
|
|
1843
|
+
topics.map((topic) => /* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
|
|
1844
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex items-start gap-4", children: [
|
|
1845
|
+
/* @__PURE__ */ jsx24("div", { className: "flex-1", children: /* @__PURE__ */ jsx24("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: topic.label }) }),
|
|
1846
|
+
/* @__PURE__ */ jsx24(
|
|
1847
|
+
"button",
|
|
1848
|
+
{
|
|
1849
|
+
type: "button",
|
|
1850
|
+
onClick: () => onToggle(topic.key),
|
|
1851
|
+
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
|
+
role: "switch",
|
|
1853
|
+
"aria-checked": topic.enabled,
|
|
1854
|
+
children: /* @__PURE__ */ jsx24(
|
|
1855
|
+
"span",
|
|
1856
|
+
{
|
|
1857
|
+
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"}`
|
|
1858
|
+
}
|
|
1859
|
+
)
|
|
1860
|
+
}
|
|
1861
|
+
)
|
|
1862
|
+
] }),
|
|
1863
|
+
/* @__PURE__ */ jsx24(
|
|
1864
|
+
"textarea",
|
|
1865
|
+
{
|
|
1866
|
+
value: topic.message,
|
|
1867
|
+
onChange: (e) => onMessageChange(topic.key, e.target.value),
|
|
1868
|
+
placeholder: labels.messagePlaceholder,
|
|
1869
|
+
maxLength: 300,
|
|
1870
|
+
rows: 2,
|
|
1871
|
+
className: "w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
1872
|
+
}
|
|
1873
|
+
)
|
|
1874
|
+
] }, topic.key)),
|
|
1875
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-3", children: [
|
|
1876
|
+
/* @__PURE__ */ jsxs17(
|
|
1877
|
+
"button",
|
|
1878
|
+
{
|
|
1879
|
+
type: "submit",
|
|
1880
|
+
disabled: saving,
|
|
1881
|
+
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
|
+
children: [
|
|
1883
|
+
/* @__PURE__ */ jsx24(Save3, { size: 14 }),
|
|
1884
|
+
labels.saveButton
|
|
1885
|
+
]
|
|
1886
|
+
}
|
|
1887
|
+
),
|
|
1888
|
+
saveSuccess && /* @__PURE__ */ jsx24("span", { className: "text-sm text-green-600 dark:text-green-400", children: labels.saveSuccess })
|
|
1889
|
+
] })
|
|
1890
|
+
] })
|
|
1891
|
+
] });
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
// src/hooks/useConversationMessages.ts
|
|
1895
|
+
import { useCallback as useCallback7 } from "react";
|
|
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
|
|
1924
|
+
function useConversationMessages(conversationId, params) {
|
|
1925
|
+
const context = useConversations();
|
|
1926
|
+
if (!context) {
|
|
1927
|
+
throw new Error("useConversationMessages requires an ancestor <ConversationsProvider>");
|
|
1928
|
+
}
|
|
1929
|
+
const { api } = context;
|
|
1930
|
+
const { data, loading, error, refetch } = useAsyncResource(
|
|
1931
|
+
() => api.fetchMessages(conversationId, params),
|
|
1932
|
+
[conversationId, params?.limit, params?.before]
|
|
1933
|
+
);
|
|
1934
|
+
const sendMessage = useCallback7(
|
|
1935
|
+
async (text) => {
|
|
1936
|
+
const message = await api.sendMessage(conversationId, text);
|
|
1937
|
+
await refetch();
|
|
1938
|
+
return message;
|
|
1939
|
+
},
|
|
1940
|
+
[api, conversationId, refetch]
|
|
1941
|
+
);
|
|
1942
|
+
const sendMedia = useCallback7(
|
|
1943
|
+
async (mediaData) => {
|
|
1944
|
+
const message = await api.sendMedia(conversationId, mediaData);
|
|
1945
|
+
await refetch();
|
|
1946
|
+
return message;
|
|
1947
|
+
},
|
|
1948
|
+
[api, conversationId, refetch]
|
|
1949
|
+
);
|
|
1950
|
+
const sendTemplate = useCallback7(
|
|
1951
|
+
async (templateData) => {
|
|
1952
|
+
await api.sendTemplate(conversationId, templateData);
|
|
1953
|
+
await refetch();
|
|
1954
|
+
},
|
|
1955
|
+
[api, conversationId, refetch]
|
|
1956
|
+
);
|
|
1957
|
+
const markRead = useCallback7(() => api.markRead(conversationId), [api, conversationId]);
|
|
1958
|
+
return { messages: data ?? [], loading, error, refetch, sendMessage, sendMedia, sendTemplate, markRead };
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// src/hooks/useConversationList.ts
|
|
1962
|
+
function useConversationList(params) {
|
|
1963
|
+
const context = useConversations();
|
|
1964
|
+
if (!context) {
|
|
1965
|
+
throw new Error("useConversationList requires an ancestor <ConversationsProvider>");
|
|
1966
|
+
}
|
|
1967
|
+
const { api } = context;
|
|
1968
|
+
const { data, loading, error, refetch } = useAsyncResource(
|
|
1969
|
+
() => api.fetchConversations(params),
|
|
1970
|
+
[params?.page, params?.limit, params?.waitingHuman, params?.search]
|
|
1971
|
+
);
|
|
1972
|
+
return { conversations: data ?? [], loading, error, refetch };
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// src/hooks/useConversationContext.ts
|
|
1976
|
+
function useConversationContext(conversationId) {
|
|
1977
|
+
const conversationsContext = useConversations();
|
|
1978
|
+
if (!conversationsContext) {
|
|
1979
|
+
throw new Error("useConversationContext requires an ancestor <ConversationsProvider>");
|
|
1980
|
+
}
|
|
1981
|
+
const { api } = conversationsContext;
|
|
1982
|
+
const { data, loading, error, refetch } = useAsyncResource(() => api.getContext(conversationId), [conversationId]);
|
|
1983
|
+
return { context: data, loading, error, refetch };
|
|
1984
|
+
}
|
|
1985
|
+
|
|
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
|
+
// src/hooks/useConversationRealtime.ts
|
|
2001
|
+
import { useEffect as useEffect6, useRef as useRef7 } from "react";
|
|
2002
|
+
function useConversationRealtime(conversationId, onEvent) {
|
|
2003
|
+
const context = useConversations();
|
|
2004
|
+
const onEventRef = useRef7(onEvent);
|
|
2005
|
+
onEventRef.current = onEvent;
|
|
2006
|
+
useEffect6(() => {
|
|
2007
|
+
if (!context || !conversationId) return;
|
|
2008
|
+
const source = context.sse.connectConversationStream(conversationId);
|
|
2009
|
+
const handler = (event) => onEventRef.current(event);
|
|
2010
|
+
source.addEventListener("message", handler);
|
|
2011
|
+
return () => {
|
|
2012
|
+
source.removeEventListener("message", handler);
|
|
2013
|
+
source.close();
|
|
2014
|
+
};
|
|
2015
|
+
}, [context, conversationId]);
|
|
2016
|
+
}
|
|
2017
|
+
function useGlobalRealtime(onEvent) {
|
|
2018
|
+
const context = useConversations();
|
|
2019
|
+
const onEventRef = useRef7(onEvent);
|
|
2020
|
+
onEventRef.current = onEvent;
|
|
2021
|
+
useEffect6(() => {
|
|
2022
|
+
if (!context) return;
|
|
2023
|
+
const source = context.sse.connectGlobalStream();
|
|
2024
|
+
const handler = (event) => onEventRef.current(event);
|
|
2025
|
+
source.addEventListener("message", handler);
|
|
2026
|
+
return () => {
|
|
2027
|
+
source.removeEventListener("message", handler);
|
|
2028
|
+
source.close();
|
|
2029
|
+
};
|
|
2030
|
+
}, [context]);
|
|
2031
|
+
}
|
|
2032
|
+
export {
|
|
2033
|
+
AudioPlayer,
|
|
2034
|
+
Avatar,
|
|
2035
|
+
ConversationListItem,
|
|
2036
|
+
ConversationLocalesProvider,
|
|
2037
|
+
ConversationWallpaper,
|
|
2038
|
+
ConversationsProvider,
|
|
2039
|
+
DateDivider,
|
|
2040
|
+
EmojiPicker,
|
|
2041
|
+
FileIcon,
|
|
2042
|
+
Lightbox,
|
|
2043
|
+
MediaRenderer,
|
|
2044
|
+
MessageBubble,
|
|
2045
|
+
MessageComposer,
|
|
2046
|
+
MessageTail,
|
|
2047
|
+
MessageText,
|
|
2048
|
+
MessageTimestamp,
|
|
2049
|
+
SimpleEmojiPicker,
|
|
2050
|
+
StatusTicks,
|
|
2051
|
+
ToastProvider,
|
|
2052
|
+
TopicsForm,
|
|
2053
|
+
WelcomeFarewellForm,
|
|
2054
|
+
WhatsAppCreateTemplateForm,
|
|
2055
|
+
WhatsAppMessageEditor,
|
|
2056
|
+
WhatsAppTemplateSettingsForm,
|
|
2057
|
+
formatFileSize,
|
|
2058
|
+
formatPhone,
|
|
2059
|
+
formatTimestamp,
|
|
2060
|
+
htmlToWA,
|
|
2061
|
+
parseWhatsAppFormatting,
|
|
2062
|
+
phoneInitials,
|
|
2063
|
+
toast,
|
|
2064
|
+
useConversationContext,
|
|
2065
|
+
useConversationDocuments,
|
|
2066
|
+
useConversationList,
|
|
2067
|
+
useConversationLocales,
|
|
2068
|
+
useConversationMessages,
|
|
2069
|
+
useConversationRealtime,
|
|
2070
|
+
useConversations,
|
|
2071
|
+
useDarkMode,
|
|
2072
|
+
useGlobalRealtime,
|
|
2073
|
+
useToast,
|
|
2074
|
+
useWaitingNotifications,
|
|
2075
|
+
waToHTML,
|
|
2076
|
+
waToHTMLInline
|
|
2077
|
+
};
|