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