@adatechnology/conversations-ui 0.1.0-rc.0 → 0.1.0-rc.10
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-FWNAWAJP.js +1743 -0
- package/dist/flows/index.js +3 -3
- package/dist/index.d.ts +619 -136
- package/dist/index.js +1064 -1024
- package/dist/preview/index.d.ts +330 -0
- package/dist/preview/index.js +1281 -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 +73 -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 +112 -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 +125 -0
- package/src/preview/ConversationPreview.tsx +315 -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/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,67 @@
|
|
|
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
|
+
cn,
|
|
27
|
+
conversationsOf,
|
|
28
|
+
createMediaUrlResolver,
|
|
29
|
+
formatDateTime,
|
|
30
|
+
formatFileSize,
|
|
31
|
+
formatPhone,
|
|
32
|
+
formatTimestamp,
|
|
33
|
+
isSameDay,
|
|
34
|
+
phoneCountryFlag,
|
|
35
|
+
phoneInitials,
|
|
36
|
+
searchEmojis,
|
|
37
|
+
totalOf,
|
|
38
|
+
useAsyncResource,
|
|
39
|
+
useConversationDocuments,
|
|
40
|
+
useConversationLocales,
|
|
41
|
+
useConversations
|
|
42
|
+
} from "./chunk-FWNAWAJP.js";
|
|
1
43
|
import {
|
|
2
44
|
htmlToWA,
|
|
3
45
|
parseWhatsAppFormatting,
|
|
4
46
|
useDarkMode,
|
|
47
|
+
useIsDarkTheme,
|
|
5
48
|
waToHTML,
|
|
6
49
|
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
|
-
};
|
|
50
|
+
} from "./chunk-2AYDBWNE.js";
|
|
645
51
|
|
|
646
52
|
// src/WhatsAppMessageEditor.tsx
|
|
647
|
-
import { useRef
|
|
53
|
+
import { useRef } from "react";
|
|
648
54
|
import { Bold, Italic, Strikethrough } from "lucide-react";
|
|
649
|
-
import { Fragment
|
|
55
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
56
|
+
var DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS = {
|
|
57
|
+
bold: "Negrito",
|
|
58
|
+
boldHint: "Negrito (*texto*)",
|
|
59
|
+
italic: "It\xE1lico",
|
|
60
|
+
italicHint: "It\xE1lico (_texto_)",
|
|
61
|
+
strikethrough: "Tachado",
|
|
62
|
+
strikethroughHint: "Tachado (~texto~)",
|
|
63
|
+
insertPlaceholder: (token) => `Inserir ${token}`
|
|
64
|
+
};
|
|
650
65
|
var INLINE_TOKEN = /(\*[^*\n]+\*|_[^_\n]+_|~[^~\n]+~|`[^`\n]+`|\{[a-zA-Z_]+\})/g;
|
|
651
66
|
function renderLine(line, lineKey) {
|
|
652
67
|
const nodes = [];
|
|
@@ -658,11 +73,11 @@ function renderLine(line, lineKey) {
|
|
|
658
73
|
if (match.index > lastIndex) nodes.push(line.slice(lastIndex, match.index));
|
|
659
74
|
const token = match[0];
|
|
660
75
|
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__ */
|
|
76
|
+
if (token.startsWith("*")) nodes.push(/* @__PURE__ */ jsx("strong", { children: token.slice(1, -1) }, key));
|
|
77
|
+
else if (token.startsWith("_")) nodes.push(/* @__PURE__ */ jsx("em", { children: token.slice(1, -1) }, key));
|
|
78
|
+
else if (token.startsWith("~")) nodes.push(/* @__PURE__ */ jsx("s", { children: token.slice(1, -1) }, key));
|
|
79
|
+
else if (token.startsWith("`")) nodes.push(/* @__PURE__ */ jsx("code", { className: "font-mono text-[0.85em]", children: token.slice(1, -1) }, key));
|
|
80
|
+
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
81
|
lastIndex = INLINE_TOKEN.lastIndex;
|
|
667
82
|
}
|
|
668
83
|
if (lastIndex < line.length) nodes.push(line.slice(lastIndex));
|
|
@@ -670,9 +85,9 @@ function renderLine(line, lineKey) {
|
|
|
670
85
|
}
|
|
671
86
|
function renderWhatsApp(text) {
|
|
672
87
|
const lines = text.split("\n");
|
|
673
|
-
return lines.map((line, index) => /* @__PURE__ */
|
|
88
|
+
return lines.map((line, index) => /* @__PURE__ */ jsxs("span", { children: [
|
|
674
89
|
renderLine(line, `ln-${index}`),
|
|
675
|
-
index < lines.length - 1 ? /* @__PURE__ */
|
|
90
|
+
index < lines.length - 1 ? /* @__PURE__ */ jsx("br", {}) : null
|
|
676
91
|
] }, `ln-${index}`));
|
|
677
92
|
}
|
|
678
93
|
function WhatsAppMessageEditor({
|
|
@@ -682,9 +97,11 @@ function WhatsAppMessageEditor({
|
|
|
682
97
|
placeholders = [],
|
|
683
98
|
rows = 4,
|
|
684
99
|
previewLabel = "Pr\xE9via (como aparece no WhatsApp)",
|
|
685
|
-
emptyPreviewText = "Sua mensagem aparecer\xE1 aqui\u2026"
|
|
100
|
+
emptyPreviewText = "Sua mensagem aparecer\xE1 aqui\u2026",
|
|
101
|
+
labels
|
|
686
102
|
}) {
|
|
687
|
-
const
|
|
103
|
+
const editorLabels = { ...DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS, ...labels };
|
|
104
|
+
const textareaRef = useRef(null);
|
|
688
105
|
function wrapSelection(marker) {
|
|
689
106
|
const textarea = textareaRef.current;
|
|
690
107
|
if (!textarea) return;
|
|
@@ -712,27 +129,27 @@ function WhatsAppMessageEditor({
|
|
|
712
129
|
});
|
|
713
130
|
}
|
|
714
131
|
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__ */
|
|
132
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
133
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5 mb-2", children: [
|
|
134
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => wrapSelection("*"), className: toolbarButtonClass, title: editorLabels.boldHint, "aria-label": editorLabels.bold, children: /* @__PURE__ */ jsx(Bold, { size: 15 }) }),
|
|
135
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => wrapSelection("_"), className: toolbarButtonClass, title: editorLabels.italicHint, "aria-label": editorLabels.italic, children: /* @__PURE__ */ jsx(Italic, { size: 15 }) }),
|
|
136
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => wrapSelection("~"), className: toolbarButtonClass, title: editorLabels.strikethroughHint, "aria-label": editorLabels.strikethrough, children: /* @__PURE__ */ jsx(Strikethrough, { size: 15 }) }),
|
|
137
|
+
placeholders.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
138
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1 h-5 w-px bg-gray-200 dark:bg-gray-600" }),
|
|
139
|
+
placeholders.map((token) => /* @__PURE__ */ jsx(
|
|
723
140
|
"button",
|
|
724
141
|
{
|
|
725
142
|
type: "button",
|
|
726
143
|
onClick: () => insertAtCursor(token),
|
|
727
144
|
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:
|
|
145
|
+
title: editorLabels.insertPlaceholder(token),
|
|
729
146
|
children: token
|
|
730
147
|
},
|
|
731
148
|
token
|
|
732
149
|
))
|
|
733
150
|
] })
|
|
734
151
|
] }),
|
|
735
|
-
/* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ jsx(
|
|
736
153
|
"textarea",
|
|
737
154
|
{
|
|
738
155
|
ref: textareaRef,
|
|
@@ -743,17 +160,17 @@ function WhatsAppMessageEditor({
|
|
|
743
160
|
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
161
|
}
|
|
745
162
|
),
|
|
746
|
-
/* @__PURE__ */
|
|
747
|
-
/* @__PURE__ */
|
|
748
|
-
/* @__PURE__ */
|
|
163
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
|
|
164
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mb-1", children: previewLabel }),
|
|
165
|
+
/* @__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
166
|
] })
|
|
750
167
|
] });
|
|
751
168
|
}
|
|
752
169
|
|
|
753
170
|
// src/SimpleEmojiPicker.tsx
|
|
754
|
-
import { useEffect
|
|
171
|
+
import { useEffect, useRef as useRef2, useState } from "react";
|
|
755
172
|
import { Smile } from "lucide-react";
|
|
756
|
-
import { jsx as
|
|
173
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
757
174
|
var EMOJIS = [
|
|
758
175
|
"\u{1F600}",
|
|
759
176
|
"\u{1F602}",
|
|
@@ -837,9 +254,9 @@ var EMOJIS = [
|
|
|
837
254
|
"\u{1F6E1}\uFE0F"
|
|
838
255
|
];
|
|
839
256
|
function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px", pickerMaxHeight = "220px" }) {
|
|
840
|
-
const [open, setOpen] =
|
|
841
|
-
const containerRef =
|
|
842
|
-
|
|
257
|
+
const [open, setOpen] = useState(false);
|
|
258
|
+
const containerRef = useRef2(null);
|
|
259
|
+
useEffect(() => {
|
|
843
260
|
if (!open) return;
|
|
844
261
|
const handleClickOutside = (e) => {
|
|
845
262
|
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
@@ -849,23 +266,23 @@ function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px",
|
|
|
849
266
|
document.addEventListener("mousedown", handleClickOutside);
|
|
850
267
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
851
268
|
}, [open]);
|
|
852
|
-
return /* @__PURE__ */
|
|
853
|
-
/* @__PURE__ */
|
|
269
|
+
return /* @__PURE__ */ jsxs2("div", { ref: containerRef, className: "relative flex-shrink-0", children: [
|
|
270
|
+
/* @__PURE__ */ jsx2(
|
|
854
271
|
"button",
|
|
855
272
|
{
|
|
856
273
|
type: "button",
|
|
857
274
|
onClick: () => setOpen((v) => !v),
|
|
858
275
|
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
276
|
title: label,
|
|
860
|
-
children: /* @__PURE__ */
|
|
277
|
+
children: /* @__PURE__ */ jsx2(Smile, { size: 15 })
|
|
861
278
|
}
|
|
862
279
|
),
|
|
863
|
-
open && /* @__PURE__ */
|
|
280
|
+
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
281
|
"div",
|
|
865
282
|
{
|
|
866
283
|
className: "grid grid-cols-10 gap-0.5 overflow-y-auto",
|
|
867
284
|
style: { width: pickerWidth, maxHeight: pickerMaxHeight },
|
|
868
|
-
children: EMOJIS.map((emoji) => /* @__PURE__ */
|
|
285
|
+
children: EMOJIS.map((emoji) => /* @__PURE__ */ jsx2(
|
|
869
286
|
"button",
|
|
870
287
|
{
|
|
871
288
|
type: "button",
|
|
@@ -883,24 +300,11 @@ function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px",
|
|
|
883
300
|
] });
|
|
884
301
|
}
|
|
885
302
|
|
|
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
303
|
// src/Avatar.tsx
|
|
903
|
-
import { jsx as
|
|
304
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
305
|
+
var DEFAULT_AVATAR_LABELS = {
|
|
306
|
+
unnamedContact: "Contato sem nome"
|
|
307
|
+
};
|
|
904
308
|
var sizeClasses = {
|
|
905
309
|
sm: "w-6 h-6 text-xs",
|
|
906
310
|
md: "w-8 h-8 text-sm",
|
|
@@ -926,10 +330,11 @@ function getBgColor(name) {
|
|
|
926
330
|
const hash = name.charCodeAt(0) + (name.charCodeAt(name.length - 1) || 0);
|
|
927
331
|
return bgColors[hash % bgColors.length];
|
|
928
332
|
}
|
|
929
|
-
function Avatar({ name, avatarUrl, size = "md", className = "" }) {
|
|
333
|
+
function Avatar({ name, avatarUrl, size = "md", className = "", labels }) {
|
|
930
334
|
const sizeClass = sizeClasses[size];
|
|
335
|
+
const unnamedContactLabel = labels?.unnamedContact ?? DEFAULT_AVATAR_LABELS.unnamedContact;
|
|
931
336
|
if (avatarUrl) {
|
|
932
|
-
return /* @__PURE__ */
|
|
337
|
+
return /* @__PURE__ */ jsx3(
|
|
933
338
|
"img",
|
|
934
339
|
{
|
|
935
340
|
src: avatarUrl,
|
|
@@ -938,14 +343,24 @@ function Avatar({ name, avatarUrl, size = "md", className = "" }) {
|
|
|
938
343
|
}
|
|
939
344
|
);
|
|
940
345
|
}
|
|
941
|
-
const initials = getInitials(name);
|
|
942
346
|
const bgColor = getBgColor(name);
|
|
943
|
-
|
|
347
|
+
if (!name) {
|
|
348
|
+
return /* @__PURE__ */ jsx3(
|
|
349
|
+
"div",
|
|
350
|
+
{
|
|
351
|
+
className: `${sizeClass} ${bgColor} rounded-full flex items-center justify-center text-white flex-shrink-0 ${className}`,
|
|
352
|
+
role: "img",
|
|
353
|
+
"aria-label": unnamedContactLabel,
|
|
354
|
+
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" }) })
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
return /* @__PURE__ */ jsx3(
|
|
944
359
|
"div",
|
|
945
360
|
{
|
|
946
361
|
className: `${sizeClass} ${bgColor} rounded-full flex items-center justify-center text-white font-semibold flex-shrink-0 ${className}`,
|
|
947
|
-
title: name
|
|
948
|
-
children:
|
|
362
|
+
title: name,
|
|
363
|
+
children: getInitials(name)
|
|
949
364
|
}
|
|
950
365
|
);
|
|
951
366
|
}
|
|
@@ -953,30 +368,91 @@ function Avatar({ name, avatarUrl, size = "md", className = "" }) {
|
|
|
953
368
|
// src/ConversationListItem.tsx
|
|
954
369
|
import { useMemo } from "react";
|
|
955
370
|
|
|
956
|
-
// src/
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
371
|
+
// src/conversationChannel.ts
|
|
372
|
+
var CONVERSATION_CHANNEL = {
|
|
373
|
+
WHATSAPP: "whatsapp",
|
|
374
|
+
MESSENGER: "messenger",
|
|
375
|
+
INSTAGRAM: "instagram",
|
|
376
|
+
WEBCHAT: "webchat"
|
|
377
|
+
};
|
|
378
|
+
var DEFAULT_CONVERSATION_CHANNEL = CONVERSATION_CHANNEL.WHATSAPP;
|
|
379
|
+
var REOPEN_MECHANISM = {
|
|
380
|
+
TEMPLATE: "template",
|
|
381
|
+
TAG: "tag",
|
|
382
|
+
NONE: "none"
|
|
383
|
+
};
|
|
384
|
+
var HANDLE_KIND = {
|
|
385
|
+
PHONE: "phone",
|
|
386
|
+
USERNAME: "username",
|
|
387
|
+
SESSION: "session"
|
|
388
|
+
};
|
|
389
|
+
var CHANNEL_CAPABILITIES = {
|
|
390
|
+
[CONVERSATION_CHANNEL.WHATSAPP]: {
|
|
391
|
+
label: "WhatsApp",
|
|
392
|
+
icon: "\u{1F4AC}",
|
|
393
|
+
hasSessionWindow: true,
|
|
394
|
+
windowHours: 24,
|
|
395
|
+
reopenMechanism: REOPEN_MECHANISM.TEMPLATE,
|
|
396
|
+
handleKind: HANDLE_KIND.PHONE
|
|
397
|
+
},
|
|
398
|
+
[CONVERSATION_CHANNEL.MESSENGER]: {
|
|
399
|
+
// Messenger também tem 24h, mas reabre com message tag — não com template aprovado.
|
|
400
|
+
label: "Messenger",
|
|
401
|
+
icon: "\u{1F4E8}",
|
|
402
|
+
hasSessionWindow: true,
|
|
403
|
+
windowHours: 24,
|
|
404
|
+
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
405
|
+
handleKind: HANDLE_KIND.USERNAME
|
|
406
|
+
},
|
|
407
|
+
[CONVERSATION_CHANNEL.INSTAGRAM]: {
|
|
408
|
+
label: "Instagram",
|
|
409
|
+
icon: "\u{1F4F7}",
|
|
410
|
+
hasSessionWindow: true,
|
|
411
|
+
windowHours: 24,
|
|
412
|
+
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
413
|
+
handleKind: HANDLE_KIND.USERNAME
|
|
414
|
+
},
|
|
415
|
+
[CONVERSATION_CHANNEL.WEBCHAT]: {
|
|
416
|
+
// Chat próprio: sem intermediário, sem janela. Bloquear o composer aqui seria inventar limite.
|
|
417
|
+
label: "Chat do site",
|
|
418
|
+
icon: "\u{1F310}",
|
|
419
|
+
hasSessionWindow: false,
|
|
420
|
+
windowHours: 0,
|
|
421
|
+
reopenMechanism: REOPEN_MECHANISM.NONE,
|
|
422
|
+
handleKind: HANDLE_KIND.SESSION
|
|
970
423
|
}
|
|
971
|
-
|
|
424
|
+
};
|
|
425
|
+
function capabilitiesOf(channel) {
|
|
426
|
+
return CHANNEL_CAPABILITIES[channel ?? DEFAULT_CONVERSATION_CHANNEL];
|
|
427
|
+
}
|
|
428
|
+
var CHANNEL_FILTER_ALL = "all";
|
|
429
|
+
function channelFiltersFor(conversations) {
|
|
430
|
+
const present = new Set(
|
|
431
|
+
conversations.map((conversation) => conversation.channel ?? DEFAULT_CONVERSATION_CHANNEL)
|
|
432
|
+
);
|
|
433
|
+
if (present.size < 2) return [];
|
|
434
|
+
const ordered = Object.keys(CHANNEL_CAPABILITIES).filter((channel) => present.has(channel));
|
|
435
|
+
return [
|
|
436
|
+
{ value: CHANNEL_FILTER_ALL, label: "Todos" },
|
|
437
|
+
...ordered.map((channel) => ({ value: channel, label: CHANNEL_CAPABILITIES[channel].label }))
|
|
438
|
+
];
|
|
972
439
|
}
|
|
973
|
-
function
|
|
974
|
-
const
|
|
975
|
-
return
|
|
440
|
+
function formatContactHandle(params) {
|
|
441
|
+
const { handleKind } = capabilitiesOf(params.channel);
|
|
442
|
+
if (handleKind === HANDLE_KIND.PHONE) return formatPhone(params.handle);
|
|
443
|
+
if (handleKind === HANDLE_KIND.USERNAME) return params.handle.startsWith("@") ? params.handle : `@${params.handle}`;
|
|
444
|
+
return `Visitante ${params.handle.slice(-6)}`;
|
|
445
|
+
}
|
|
446
|
+
function contactFlag(params) {
|
|
447
|
+
return capabilitiesOf(params.channel).handleKind === HANDLE_KIND.PHONE ? phoneCountryFlag(params.handle) : "";
|
|
976
448
|
}
|
|
977
449
|
|
|
978
450
|
// src/ConversationListItem.tsx
|
|
979
|
-
import { jsx as
|
|
451
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
452
|
+
var DEFAULT_CONVERSATION_LIST_ITEM_LABELS = {
|
|
453
|
+
expiredWindow: "Janela expirada",
|
|
454
|
+
warningWindow: "Janela pr\xF3xima do fim"
|
|
455
|
+
};
|
|
980
456
|
function formatRelativeTime(dateStr) {
|
|
981
457
|
try {
|
|
982
458
|
const date = new Date(dateStr);
|
|
@@ -1035,50 +511,63 @@ var ConversationListItem = ({
|
|
|
1035
511
|
active = false,
|
|
1036
512
|
selected = false,
|
|
1037
513
|
onClick,
|
|
1038
|
-
onSelect
|
|
514
|
+
onSelect,
|
|
515
|
+
showDivider = true,
|
|
516
|
+
highlightActive = true,
|
|
517
|
+
labels
|
|
1039
518
|
}) => {
|
|
519
|
+
const expiredWindowLabel = labels?.expiredWindow ?? DEFAULT_CONVERSATION_LIST_ITEM_LABELS.expiredWindow;
|
|
520
|
+
const warningWindowLabel = labels?.warningWindow ?? DEFAULT_CONVERSATION_LIST_ITEM_LABELS.warningWindow;
|
|
1040
521
|
const isActive = active || selected;
|
|
1041
522
|
const windowStatus = useMemo(() => getWindowStatus(conversation.lastInboundAt), [conversation.lastInboundAt]);
|
|
1042
523
|
const preview = useMemo(() => lastMessagePreview(conversation), [conversation.lastContent]);
|
|
1043
|
-
const
|
|
524
|
+
const handle = conversation.contactId ?? conversation.whatsappNumber;
|
|
525
|
+
const displayHandle = formatContactHandle({ handle, channel: conversation.channel });
|
|
526
|
+
const name = conversation.clientName || displayHandle || handle;
|
|
1044
527
|
const timestamp = formatRelativeTime(conversation.lastAt);
|
|
528
|
+
const flag = contactFlag({ handle, channel: conversation.channel });
|
|
1045
529
|
const isInbound = conversation.lastDirection === "inbound";
|
|
1046
530
|
const handleClick = () => {
|
|
1047
531
|
onClick?.();
|
|
1048
532
|
onSelect?.(conversation.id);
|
|
1049
533
|
};
|
|
1050
|
-
return /* @__PURE__ */
|
|
534
|
+
return /* @__PURE__ */ jsxs3(
|
|
1051
535
|
"button",
|
|
1052
536
|
{
|
|
1053
537
|
onClick: handleClick,
|
|
1054
|
-
className:
|
|
538
|
+
className: `w-full flex items-center gap-3 px-3 py-2.5 text-left transition-colors ${highlightActive ? "hover:bg-[#f0f2f5]" : ""} ${showDivider ? "border-b border-[#e9edef]" : ""}`,
|
|
1055
539
|
style: {
|
|
1056
|
-
backgroundColor: isActive ? "#f0f2f5" : "transparent"
|
|
540
|
+
backgroundColor: isActive && highlightActive ? "#f0f2f5" : "transparent"
|
|
1057
541
|
},
|
|
1058
542
|
children: [
|
|
1059
|
-
/* @__PURE__ */
|
|
1060
|
-
/* @__PURE__ */
|
|
1061
|
-
conversation.waitingHuman && /* @__PURE__ */
|
|
543
|
+
/* @__PURE__ */ jsxs3("div", { className: "relative flex-shrink-0", children: [
|
|
544
|
+
/* @__PURE__ */ jsx4(Avatar, { name: conversation.clientName, size: "lg" }),
|
|
545
|
+
conversation.waitingHuman && /* @__PURE__ */ jsx4("div", { className: "absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 bg-amber-400 rounded-full border-2 border-white" })
|
|
1062
546
|
] }),
|
|
1063
|
-
/* @__PURE__ */
|
|
1064
|
-
/* @__PURE__ */
|
|
1065
|
-
/* @__PURE__ */
|
|
1066
|
-
/* @__PURE__ */
|
|
1067
|
-
|
|
1068
|
-
windowStatus && windowStatus.label === "
|
|
547
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
|
|
548
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between gap-2", children: [
|
|
549
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5 min-w-0", children: [
|
|
550
|
+
!conversation.clientName && flag ? /* @__PURE__ */ jsx4("span", { "aria-hidden": true, children: flag }) : null,
|
|
551
|
+
/* @__PURE__ */ jsx4("span", { className: "text-[16px] text-[#111b21] truncate", children: name }),
|
|
552
|
+
windowStatus && windowStatus.label === "expired" && /* @__PURE__ */ jsx4("span", { className: "w-2 h-2 rounded-full bg-red-500 flex-shrink-0", title: expiredWindowLabel }),
|
|
553
|
+
windowStatus && windowStatus.label === "warning" && /* @__PURE__ */ jsx4("span", { className: "w-2 h-2 rounded-full bg-orange-500 flex-shrink-0", title: warningWindowLabel })
|
|
1069
554
|
] }),
|
|
1070
|
-
timestamp && /* @__PURE__ */
|
|
555
|
+
timestamp && /* @__PURE__ */ jsx4("span", { className: "text-xs text-[#667781] flex-shrink-0", children: timestamp })
|
|
1071
556
|
] }),
|
|
1072
|
-
/* @__PURE__ */
|
|
1073
|
-
/* @__PURE__ */
|
|
1074
|
-
|
|
557
|
+
conversation.clientName ? /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 text-xs text-[#667781]", children: [
|
|
558
|
+
flag ? /* @__PURE__ */ jsx4("span", { "aria-hidden": true, children: flag }) : null,
|
|
559
|
+
/* @__PURE__ */ jsx4("span", { className: "truncate", children: displayHandle })
|
|
560
|
+
] }) : null,
|
|
561
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between mt-0.5", children: [
|
|
562
|
+
/* @__PURE__ */ jsxs3("span", { className: "text-sm text-[#667781] truncate max-w-[180px]", children: [
|
|
563
|
+
preview.icon && /* @__PURE__ */ jsx4("span", { className: "mr-1", children: preview.icon }),
|
|
1075
564
|
preview.preview || "Sem mensagens"
|
|
1076
565
|
] }),
|
|
1077
|
-
conversation.unread > 0 && /* @__PURE__ */
|
|
566
|
+
conversation.unread > 0 && /* @__PURE__ */ jsx4("span", { className: "bg-[#25d366] text-white text-xs font-semibold rounded-full min-w-[20px] h-5 px-1.5 flex items-center justify-center flex-shrink-0 ml-2", children: conversation.unread > 99 ? "99+" : conversation.unread })
|
|
1078
567
|
] }),
|
|
1079
|
-
/* @__PURE__ */
|
|
1080
|
-
conversation.mode === "human" && /* @__PURE__ */
|
|
1081
|
-
conversation.mode === "bot" && conversation.waitingHuman && /* @__PURE__ */
|
|
568
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5 mt-1", children: [
|
|
569
|
+
conversation.mode === "human" && /* @__PURE__ */ jsx4("span", { className: "text-[10px] px-1.5 py-0.5 rounded font-medium bg-purple-100 text-purple-700", children: "Humano" }),
|
|
570
|
+
conversation.mode === "bot" && conversation.waitingHuman && /* @__PURE__ */ jsx4("span", { className: "text-[10px] px-1.5 py-0.5 rounded font-medium bg-amber-100 text-amber-700", children: "Aguardando" })
|
|
1082
571
|
] })
|
|
1083
572
|
] })
|
|
1084
573
|
]
|
|
@@ -1087,9 +576,9 @@ var ConversationListItem = ({
|
|
|
1087
576
|
};
|
|
1088
577
|
|
|
1089
578
|
// src/Toast.tsx
|
|
1090
|
-
import { createContext
|
|
1091
|
-
import { jsx as
|
|
1092
|
-
var ToastContext =
|
|
579
|
+
import { createContext, useContext, useState as useState2, useCallback, useEffect as useEffect2 } from "react";
|
|
580
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
581
|
+
var ToastContext = createContext(null);
|
|
1093
582
|
var nextId = 0;
|
|
1094
583
|
var singletonShow = null;
|
|
1095
584
|
var toast = {
|
|
@@ -1105,34 +594,34 @@ var typeStyles = {
|
|
|
1105
594
|
info: "bg-blue-600"
|
|
1106
595
|
};
|
|
1107
596
|
var typeIcons = {
|
|
1108
|
-
success: /* @__PURE__ */
|
|
1109
|
-
error: /* @__PURE__ */
|
|
1110
|
-
/* @__PURE__ */
|
|
1111
|
-
/* @__PURE__ */
|
|
597
|
+
success: /* @__PURE__ */ jsx5("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx5("polyline", { points: "20 6 9 17 4 12" }) }),
|
|
598
|
+
error: /* @__PURE__ */ jsxs4("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
599
|
+
/* @__PURE__ */ jsx5("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
600
|
+
/* @__PURE__ */ jsx5("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1112
601
|
] }),
|
|
1113
|
-
info: /* @__PURE__ */
|
|
1114
|
-
/* @__PURE__ */
|
|
1115
|
-
/* @__PURE__ */
|
|
1116
|
-
/* @__PURE__ */
|
|
602
|
+
info: /* @__PURE__ */ jsxs4("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
603
|
+
/* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "10" }),
|
|
604
|
+
/* @__PURE__ */ jsx5("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
|
|
605
|
+
/* @__PURE__ */ jsx5("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
|
|
1117
606
|
] })
|
|
1118
607
|
};
|
|
1119
608
|
function ToastItemComponent({ item, onRemove }) {
|
|
1120
|
-
const [exiting, setExiting] =
|
|
1121
|
-
|
|
609
|
+
const [exiting, setExiting] = useState2(false);
|
|
610
|
+
useEffect2(() => {
|
|
1122
611
|
const timer = setTimeout(() => {
|
|
1123
612
|
setExiting(true);
|
|
1124
613
|
setTimeout(() => onRemove(item.id), 300);
|
|
1125
614
|
}, 5e3);
|
|
1126
615
|
return () => clearTimeout(timer);
|
|
1127
616
|
}, [item.id, onRemove]);
|
|
1128
|
-
return /* @__PURE__ */
|
|
617
|
+
return /* @__PURE__ */ jsxs4(
|
|
1129
618
|
"div",
|
|
1130
619
|
{
|
|
1131
620
|
className: `flex items-center gap-2 px-4 py-3 rounded-lg shadow-lg text-white text-sm min-w-[280px] max-w-[400px] ${typeStyles[item.type]} ${exiting ? "animate-slide-out" : "animate-slide-in"}`,
|
|
1132
621
|
children: [
|
|
1133
|
-
/* @__PURE__ */
|
|
1134
|
-
/* @__PURE__ */
|
|
1135
|
-
/* @__PURE__ */
|
|
622
|
+
/* @__PURE__ */ jsx5("span", { className: "flex-shrink-0", children: typeIcons[item.type] }),
|
|
623
|
+
/* @__PURE__ */ jsx5("span", { className: "flex-1", children: item.message }),
|
|
624
|
+
/* @__PURE__ */ jsx5(
|
|
1136
625
|
"button",
|
|
1137
626
|
{
|
|
1138
627
|
onClick: () => {
|
|
@@ -1140,9 +629,9 @@ function ToastItemComponent({ item, onRemove }) {
|
|
|
1140
629
|
setTimeout(() => onRemove(item.id), 300);
|
|
1141
630
|
},
|
|
1142
631
|
className: "flex-shrink-0 opacity-70 hover:opacity-100 transition-opacity",
|
|
1143
|
-
children: /* @__PURE__ */
|
|
1144
|
-
/* @__PURE__ */
|
|
1145
|
-
/* @__PURE__ */
|
|
632
|
+
children: /* @__PURE__ */ jsxs4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
633
|
+
/* @__PURE__ */ jsx5("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
634
|
+
/* @__PURE__ */ jsx5("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1146
635
|
] })
|
|
1147
636
|
}
|
|
1148
637
|
)
|
|
@@ -1151,24 +640,24 @@ function ToastItemComponent({ item, onRemove }) {
|
|
|
1151
640
|
);
|
|
1152
641
|
}
|
|
1153
642
|
function ToastProvider({ children }) {
|
|
1154
|
-
const [items, setItems] =
|
|
1155
|
-
const show =
|
|
643
|
+
const [items, setItems] = useState2([]);
|
|
644
|
+
const show = useCallback((type, message) => {
|
|
1156
645
|
const id = ++nextId;
|
|
1157
646
|
setItems((prev) => [...prev, { id, type, message }]);
|
|
1158
647
|
}, []);
|
|
1159
|
-
const remove =
|
|
648
|
+
const remove = useCallback((id) => {
|
|
1160
649
|
setItems((prev) => prev.filter((item) => item.id !== id));
|
|
1161
650
|
}, []);
|
|
1162
|
-
|
|
651
|
+
useEffect2(() => {
|
|
1163
652
|
singletonShow = show;
|
|
1164
653
|
return () => {
|
|
1165
654
|
singletonShow = null;
|
|
1166
655
|
};
|
|
1167
656
|
}, [show]);
|
|
1168
|
-
return /* @__PURE__ */
|
|
657
|
+
return /* @__PURE__ */ jsxs4(ToastContext.Provider, { value: { show }, children: [
|
|
1169
658
|
children,
|
|
1170
|
-
/* @__PURE__ */
|
|
1171
|
-
/* @__PURE__ */
|
|
659
|
+
/* @__PURE__ */ jsx5("div", { className: "fixed top-4 right-4 z-[100] flex flex-col gap-2", children: items.map((item) => /* @__PURE__ */ jsx5(ToastItemComponent, { item, onRemove: remove }, item.id)) }),
|
|
660
|
+
/* @__PURE__ */ jsx5("style", { children: `
|
|
1172
661
|
@keyframes slide-in {
|
|
1173
662
|
from { transform: translateX(100%); opacity: 0; }
|
|
1174
663
|
to { transform: translateX(0); opacity: 1; }
|
|
@@ -1183,7 +672,7 @@ function ToastProvider({ children }) {
|
|
|
1183
672
|
] });
|
|
1184
673
|
}
|
|
1185
674
|
function useToast() {
|
|
1186
|
-
const context =
|
|
675
|
+
const context = useContext(ToastContext);
|
|
1187
676
|
if (!context) {
|
|
1188
677
|
throw new Error("useToast must be used within a ToastProvider");
|
|
1189
678
|
}
|
|
@@ -1191,11 +680,11 @@ function useToast() {
|
|
|
1191
680
|
}
|
|
1192
681
|
|
|
1193
682
|
// src/MessageText.tsx
|
|
1194
|
-
import { useCallback as
|
|
1195
|
-
import { jsx as
|
|
683
|
+
import { useCallback as useCallback2, useState as useState3 } from "react";
|
|
684
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1196
685
|
function MessageText({ message }) {
|
|
1197
|
-
const [copied, setCopied] =
|
|
1198
|
-
const handleCopy =
|
|
686
|
+
const [copied, setCopied] = useState3(false);
|
|
687
|
+
const handleCopy = useCallback2(async () => {
|
|
1199
688
|
if (!message.content) return;
|
|
1200
689
|
try {
|
|
1201
690
|
await navigator.clipboard.writeText(message.content);
|
|
@@ -1205,102 +694,557 @@ function MessageText({ message }) {
|
|
|
1205
694
|
}
|
|
1206
695
|
}, [message.content]);
|
|
1207
696
|
if (message.type === "template")
|
|
1208
|
-
return /* @__PURE__ */
|
|
1209
|
-
return /* @__PURE__ */
|
|
697
|
+
return /* @__PURE__ */ jsx6("p", { className: "text-sm italic text-[#667781]", children: message.content ?? "Template message" });
|
|
698
|
+
return /* @__PURE__ */ jsxs5(
|
|
1210
699
|
"div",
|
|
1211
700
|
{
|
|
1212
701
|
onClick: handleCopy,
|
|
1213
702
|
className: "text-[14.2px] leading-[19px] whitespace-pre-wrap break-words select-all [&_strong]:font-bold [&_em]:italic [&_del]:line-through",
|
|
1214
703
|
children: [
|
|
1215
|
-
/* @__PURE__ */
|
|
1216
|
-
copied && /* @__PURE__ */
|
|
704
|
+
/* @__PURE__ */ jsx6("span", { children: parseWhatsAppFormatting(message.content ?? "") }),
|
|
705
|
+
copied && /* @__PURE__ */ jsx6("span", { className: "absolute top-0 right-0 -translate-y-full bg-[#3b4a54] text-white text-[11px] px-1.5 py-0.5 rounded shadow-lg", children: "Copiado!" })
|
|
1217
706
|
]
|
|
1218
707
|
}
|
|
1219
708
|
);
|
|
1220
709
|
}
|
|
1221
710
|
|
|
1222
711
|
// src/MessageTimestamp.tsx
|
|
1223
|
-
import { jsx as
|
|
712
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1224
713
|
function MessageTimestamp({ timestamp, status, isOutbound }) {
|
|
1225
|
-
return /* @__PURE__ */
|
|
1226
|
-
/* @__PURE__ */
|
|
1227
|
-
isOutbound && status && /* @__PURE__ */
|
|
714
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-end gap-[3px]", children: [
|
|
715
|
+
/* @__PURE__ */ jsx7("span", { className: "text-[11px] text-[#667781] leading-[15px] select-none", children: timestamp }),
|
|
716
|
+
isOutbound && status && /* @__PURE__ */ jsx7("span", { className: "flex items-center -mr-[2px]", children: /* @__PURE__ */ jsx7(StatusTicks, { status }) })
|
|
1228
717
|
] });
|
|
1229
718
|
}
|
|
1230
719
|
|
|
1231
720
|
// src/MessageTail.tsx
|
|
1232
|
-
import { jsx as
|
|
721
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1233
722
|
function MessageTail({ isOutbound }) {
|
|
1234
723
|
if (isOutbound)
|
|
1235
|
-
return /* @__PURE__ */
|
|
1236
|
-
return /* @__PURE__ */
|
|
724
|
+
return /* @__PURE__ */ jsx8("svg", { className: "absolute top-0 -right-[5px] w-[5px] h-[11px]", viewBox: "0 0 5 11", fill: "#d9fdd3", children: /* @__PURE__ */ jsx8("path", { d: "M5 0H0v11c1.5-2 4.5-2 5-4V0z" }) });
|
|
725
|
+
return /* @__PURE__ */ jsx8("svg", { className: "absolute top-0 -left-[5px] w-[5px] h-[11px]", viewBox: "0 0 5 11", fill: "white", children: /* @__PURE__ */ jsx8("path", { d: "M0 0h5v11C3.5 9 .5 9 0 7V0z" }) });
|
|
1237
726
|
}
|
|
1238
727
|
|
|
1239
|
-
// src/
|
|
1240
|
-
|
|
728
|
+
// src/conversationWindow.ts
|
|
729
|
+
var MINUTE_MS = 6e4;
|
|
730
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
731
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
732
|
+
var CONVERSATION_WINDOW = {
|
|
733
|
+
ALL: "all",
|
|
734
|
+
FRESH: "fresh",
|
|
735
|
+
WARNING: "warning",
|
|
736
|
+
CRITICAL: "critical",
|
|
737
|
+
EXPIRED: "expired"
|
|
738
|
+
};
|
|
739
|
+
var WINDOW_FILTERS = [
|
|
740
|
+
{ value: CONVERSATION_WINDOW.ALL, label: "Todas", dotClass: "" },
|
|
741
|
+
{ value: CONVERSATION_WINDOW.FRESH, label: "<12h", dotClass: "bg-green-500" },
|
|
742
|
+
{ value: CONVERSATION_WINDOW.WARNING, label: "12-21h", dotClass: "bg-yellow-500" },
|
|
743
|
+
{ value: CONVERSATION_WINDOW.CRITICAL, label: "21-24h", dotClass: "bg-red-500" },
|
|
744
|
+
{ value: CONVERSATION_WINDOW.EXPIRED, label: ">24h", dotClass: "bg-gray-400" }
|
|
745
|
+
];
|
|
746
|
+
var WARNING_RATIO = 0.5;
|
|
747
|
+
var CRITICAL_RATIO = 0.875;
|
|
748
|
+
function windowOf(params) {
|
|
749
|
+
const capabilities = capabilitiesOf(params.channel);
|
|
750
|
+
if (!capabilities.hasSessionWindow) return CONVERSATION_WINDOW.FRESH;
|
|
751
|
+
if (!params.lastInboundAt) return CONVERSATION_WINDOW.EXPIRED;
|
|
752
|
+
const windowMs = capabilities.windowHours * HOUR_MS;
|
|
753
|
+
const elapsed = params.now - new Date(params.lastInboundAt).getTime();
|
|
754
|
+
if (elapsed >= windowMs) return CONVERSATION_WINDOW.EXPIRED;
|
|
755
|
+
if (elapsed >= windowMs * CRITICAL_RATIO) return CONVERSATION_WINDOW.CRITICAL;
|
|
756
|
+
if (elapsed >= windowMs * WARNING_RATIO) return CONVERSATION_WINDOW.WARNING;
|
|
757
|
+
return CONVERSATION_WINDOW.FRESH;
|
|
758
|
+
}
|
|
759
|
+
function formatStalledFor(lastAt, now) {
|
|
760
|
+
const elapsed = Math.max(0, now - new Date(lastAt).getTime());
|
|
761
|
+
const days = Math.floor(elapsed / DAY_MS);
|
|
762
|
+
const hours = Math.floor(elapsed % DAY_MS / HOUR_MS);
|
|
763
|
+
const minutes = Math.floor(elapsed % HOUR_MS / MINUTE_MS);
|
|
764
|
+
if (days > 0) return `${days}d ${hours}h:${String(minutes).padStart(2, "0")}m`;
|
|
765
|
+
if (hours > 0) return `${hours}h:${String(minutes).padStart(2, "0")}m`;
|
|
766
|
+
return `${minutes}m`;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/ChannelIcon.tsx
|
|
770
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
771
|
+
var CHANNEL_BRAND_COLOR = {
|
|
772
|
+
[CONVERSATION_CHANNEL.WHATSAPP]: "#25D366",
|
|
773
|
+
[CONVERSATION_CHANNEL.MESSENGER]: "#0084FF",
|
|
774
|
+
[CONVERSATION_CHANNEL.INSTAGRAM]: "#E4405F",
|
|
775
|
+
[CONVERSATION_CHANNEL.WEBCHAT]: "#64748B"
|
|
776
|
+
};
|
|
777
|
+
function Glyph({ channel }) {
|
|
778
|
+
const color = CHANNEL_BRAND_COLOR[channel];
|
|
779
|
+
if (channel === CONVERSATION_CHANNEL.WHATSAPP) {
|
|
780
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
781
|
+
/* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
782
|
+
/* @__PURE__ */ jsx9(
|
|
783
|
+
"path",
|
|
784
|
+
{
|
|
785
|
+
d: "M8.6 7.2c.3-.1.7 0 .9.3l1 1.6c.2.3.1.7-.1.9l-.7.7c.6 1.3 1.6 2.3 2.9 2.9l.7-.7c.2-.2.6-.3.9-.1l1.6 1c.3.2.4.6.3.9-.4 1-1.4 1.6-2.4 1.4-3-.5-5.4-2.9-5.9-5.9-.2-1 .4-2 1.4-2.4Z",
|
|
786
|
+
fill: "#fff"
|
|
787
|
+
}
|
|
788
|
+
)
|
|
789
|
+
] });
|
|
790
|
+
}
|
|
791
|
+
if (channel === CONVERSATION_CHANNEL.MESSENGER) {
|
|
792
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
793
|
+
/* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
794
|
+
/* @__PURE__ */ jsx9("path", { d: "M6.5 15.5 11 10.8l2.4 2.4L17.5 9l-4.6 4.9-2.4-2.4-4 4Z", fill: "#fff", stroke: "#fff", strokeWidth: "1.4", strokeLinejoin: "round" })
|
|
795
|
+
] });
|
|
796
|
+
}
|
|
797
|
+
if (channel === CONVERSATION_CHANNEL.INSTAGRAM) {
|
|
798
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
799
|
+
/* @__PURE__ */ jsx9("rect", { x: "2", y: "2", width: "20", height: "20", rx: "6", fill: color }),
|
|
800
|
+
/* @__PURE__ */ jsx9("rect", { x: "6.5", y: "6.5", width: "11", height: "11", rx: "3.5", fill: "none", stroke: "#fff", strokeWidth: "1.6" }),
|
|
801
|
+
/* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "2.6", fill: "none", stroke: "#fff", strokeWidth: "1.6" }),
|
|
802
|
+
/* @__PURE__ */ jsx9("circle", { cx: "16.4", cy: "7.6", r: "1", fill: "#fff" })
|
|
803
|
+
] });
|
|
804
|
+
}
|
|
805
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
806
|
+
/* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
807
|
+
/* @__PURE__ */ jsx9("path", { d: "M6.5 8.5h11v7h-5.5L8.5 18v-2.5h-2v-7Z", fill: "#fff" })
|
|
808
|
+
] });
|
|
809
|
+
}
|
|
810
|
+
function ChannelIcon({ channel, size = 14, className = "" }) {
|
|
811
|
+
const resolved = channel ?? CONVERSATION_CHANNEL.WHATSAPP;
|
|
812
|
+
const { label } = capabilitiesOf(resolved);
|
|
813
|
+
return /* @__PURE__ */ jsx9(
|
|
814
|
+
"svg",
|
|
815
|
+
{
|
|
816
|
+
viewBox: "0 0 24 24",
|
|
817
|
+
width: size,
|
|
818
|
+
height: size,
|
|
819
|
+
className: `inline-block shrink-0 ${className}`.trim(),
|
|
820
|
+
role: "img",
|
|
821
|
+
"aria-label": label,
|
|
822
|
+
children: /* @__PURE__ */ jsx9(Glyph, { channel: resolved })
|
|
823
|
+
}
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/ConversationRow.tsx
|
|
828
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
829
|
+
var WINDOW_TITLE = {
|
|
830
|
+
[CONVERSATION_WINDOW.ALL]: "",
|
|
831
|
+
[CONVERSATION_WINDOW.FRESH]: "Janela de 24h aberta (menos de 12h)",
|
|
832
|
+
[CONVERSATION_WINDOW.WARNING]: "Janela de 24h fechando (12-21h)",
|
|
833
|
+
[CONVERSATION_WINDOW.CRITICAL]: "Janela de 24h quase expirada (21-24h)",
|
|
834
|
+
[CONVERSATION_WINDOW.EXPIRED]: "Janela de 24h expirada \u2014 s\xF3 template"
|
|
835
|
+
};
|
|
836
|
+
var STALLED_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
837
|
+
var WINDOW_BAR_CLASS = {
|
|
838
|
+
[CONVERSATION_WINDOW.ALL]: "bg-transparent",
|
|
839
|
+
[CONVERSATION_WINDOW.FRESH]: "bg-green-500",
|
|
840
|
+
[CONVERSATION_WINDOW.WARNING]: "bg-yellow-500",
|
|
841
|
+
[CONVERSATION_WINDOW.CRITICAL]: "bg-red-500",
|
|
842
|
+
[CONVERSATION_WINDOW.EXPIRED]: "bg-gray-300"
|
|
843
|
+
};
|
|
844
|
+
function ConversationRow({
|
|
845
|
+
conversation,
|
|
846
|
+
active,
|
|
847
|
+
selected,
|
|
848
|
+
now,
|
|
849
|
+
busy,
|
|
850
|
+
onOpen,
|
|
851
|
+
onToggleSelected,
|
|
852
|
+
onTakeover,
|
|
853
|
+
className,
|
|
854
|
+
classNames
|
|
855
|
+
}) {
|
|
856
|
+
const capabilities = capabilitiesOf(conversation.channel);
|
|
857
|
+
const window2 = windowOf({ lastInboundAt: conversation.lastInboundAt, now, channel: conversation.channel });
|
|
858
|
+
const stalledMs = now - new Date(conversation.lastAt).getTime();
|
|
859
|
+
const isStalled = stalledMs > STALLED_THRESHOLD_MS;
|
|
860
|
+
const isWaiting = conversation.mode === "bot" && conversation.waitingHuman;
|
|
861
|
+
return /* @__PURE__ */ jsxs8("div", { className: cn("cv-row flex border-b", active && "cv-row--active", classNames?.root, className), children: [
|
|
862
|
+
/* @__PURE__ */ jsx10(
|
|
863
|
+
"span",
|
|
864
|
+
{
|
|
865
|
+
className: cn("w-1 shrink-0", WINDOW_BAR_CLASS[window2], classNames?.windowBar),
|
|
866
|
+
title: WINDOW_TITLE[window2],
|
|
867
|
+
"aria-label": WINDOW_TITLE[window2]
|
|
868
|
+
}
|
|
869
|
+
),
|
|
870
|
+
/* @__PURE__ */ jsxs8("div", { className: "min-w-0 flex-1", children: [
|
|
871
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-start gap-2 px-2 pt-2", children: [
|
|
872
|
+
/* @__PURE__ */ jsx10(
|
|
873
|
+
"input",
|
|
874
|
+
{
|
|
875
|
+
type: "checkbox",
|
|
876
|
+
checked: selected,
|
|
877
|
+
onChange: onToggleSelected,
|
|
878
|
+
"aria-label": `Selecionar conversa ${conversation.whatsappNumber}`,
|
|
879
|
+
className: "mt-3"
|
|
880
|
+
}
|
|
881
|
+
),
|
|
882
|
+
/* @__PURE__ */ jsx10("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx10(
|
|
883
|
+
ConversationListItem,
|
|
884
|
+
{
|
|
885
|
+
conversation,
|
|
886
|
+
active,
|
|
887
|
+
onClick: onOpen,
|
|
888
|
+
showDivider: false,
|
|
889
|
+
highlightActive: false
|
|
890
|
+
}
|
|
891
|
+
) })
|
|
892
|
+
] }),
|
|
893
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-center gap-1.5 px-4 pb-2 pl-8", children: [
|
|
894
|
+
/* @__PURE__ */ jsxs8("span", { className: "cv-pill", children: [
|
|
895
|
+
/* @__PURE__ */ jsx10(ChannelIcon, { channel: conversation.channel }),
|
|
896
|
+
" ",
|
|
897
|
+
capabilities.label
|
|
898
|
+
] }),
|
|
899
|
+
conversation.mode === "human" ? /* @__PURE__ */ jsx10("span", { className: "cv-pill cv-pill--success", children: "\u{1F9D1}\u200D\u{1F4BC} em atendimento" }) : null,
|
|
900
|
+
isWaiting ? /* @__PURE__ */ jsx10("span", { className: "cv-pill cv-pill--warning", children: "\u23F3 aguardando atendimento" }) : null,
|
|
901
|
+
!isWaiting && conversation.mode === "bot" ? /* @__PURE__ */ jsx10("span", { className: "cv-pill", children: "\u{1F916} bot ativo" }) : null,
|
|
902
|
+
isStalled ? /* @__PURE__ */ jsxs8("span", { className: "cv-pill cv-pill--danger", children: [
|
|
903
|
+
"\u23F1\uFE0F parada h\xE1 ",
|
|
904
|
+
formatStalledFor(conversation.lastAt, now)
|
|
905
|
+
] }) : null,
|
|
906
|
+
isWaiting ? /* @__PURE__ */ jsx10("button", { type: "button", onClick: onTakeover, disabled: busy, className: "cv-header-action", children: "\u25B6\uFE0F Continuar Atendimento" }) : null
|
|
907
|
+
] })
|
|
908
|
+
] })
|
|
909
|
+
] });
|
|
910
|
+
}
|
|
1241
911
|
|
|
1242
|
-
// src/
|
|
1243
|
-
import {
|
|
1244
|
-
import { jsx as
|
|
1245
|
-
var
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
912
|
+
// src/ConversationHeader.tsx
|
|
913
|
+
import { useState as useState4 } from "react";
|
|
914
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
915
|
+
var DEFAULT_CONVERSATION_HEADER_LABELS = {
|
|
916
|
+
botMode: "\u{1F916} atendimento autom\xE1tico",
|
|
917
|
+
humanMode: "\u{1F9D1}\u200D\u{1F4BC} atendimento humano",
|
|
918
|
+
returnToBot: "Devolver ao bot",
|
|
919
|
+
finish: "Finalizar",
|
|
920
|
+
takeover: "Assumir atendimento",
|
|
921
|
+
download: "Baixar conversa",
|
|
922
|
+
documents: "Arquivos da conversa",
|
|
923
|
+
back: "Voltar para a lista",
|
|
924
|
+
moreActions: "Mais a\xE7\xF5es"
|
|
925
|
+
};
|
|
926
|
+
function ConversationHeader({
|
|
927
|
+
conversation,
|
|
928
|
+
busy = false,
|
|
929
|
+
onTakeover,
|
|
930
|
+
onReturnToBot,
|
|
931
|
+
onFinish,
|
|
932
|
+
onDownload,
|
|
933
|
+
onOpenDocuments,
|
|
934
|
+
documentsOpen = false,
|
|
935
|
+
extraUtilities,
|
|
936
|
+
onBack,
|
|
937
|
+
labels: labelsOverride,
|
|
938
|
+
className,
|
|
939
|
+
classNames
|
|
1250
940
|
}) {
|
|
1251
|
-
|
|
941
|
+
const labels = { ...DEFAULT_CONVERSATION_HEADER_LABELS, ...labelsOverride };
|
|
942
|
+
const isHuman = conversation.mode === "human";
|
|
943
|
+
const handle = conversation.contactId ?? conversation.whatsappNumber;
|
|
944
|
+
const displayHandle = formatContactHandle({ handle, channel: conversation.channel });
|
|
945
|
+
const flag = contactFlag({ handle, channel: conversation.channel });
|
|
946
|
+
const capabilities = capabilitiesOf(conversation.channel);
|
|
947
|
+
const [menuOpen, setMenuOpen] = useState4(false);
|
|
948
|
+
const utilities = [
|
|
949
|
+
onOpenDocuments ? { key: "documents", icon: "\u{1F4C4}", label: labels.documents, run: onOpenDocuments, active: documentsOpen } : void 0,
|
|
950
|
+
onDownload ? { key: "download", icon: "\u2B07\uFE0F", label: labels.download, run: onDownload, active: false } : void 0,
|
|
951
|
+
...(extraUtilities ?? []).map((utility) => ({ ...utility, active: utility.active ?? false }))
|
|
952
|
+
].filter(
|
|
953
|
+
(utility) => Boolean(utility)
|
|
954
|
+
);
|
|
955
|
+
const actions = [
|
|
956
|
+
!isHuman && onTakeover ? { key: "takeover", label: labels.takeover, run: onTakeover, className: "cv-header-action--primary" } : void 0,
|
|
957
|
+
isHuman && onReturnToBot ? { key: "release", label: labels.returnToBot, run: onReturnToBot, className: "" } : void 0,
|
|
958
|
+
isHuman && onFinish ? { key: "finish", label: `\u2715 ${labels.finish}`, run: onFinish, className: "cv-header-action--danger" } : void 0
|
|
959
|
+
].filter((action) => Boolean(action));
|
|
960
|
+
const menuItems = [
|
|
961
|
+
...utilities.map((utility) => ({
|
|
962
|
+
key: utility.key,
|
|
963
|
+
label: `${utility.icon} ${utility.label}`,
|
|
964
|
+
run: utility.run,
|
|
965
|
+
className: ""
|
|
966
|
+
})),
|
|
967
|
+
...actions
|
|
968
|
+
];
|
|
969
|
+
return /* @__PURE__ */ jsxs9(
|
|
970
|
+
"header",
|
|
971
|
+
{
|
|
972
|
+
className: cn(
|
|
973
|
+
"flex flex-nowrap items-center justify-between gap-3 border-b px-4 py-3",
|
|
974
|
+
classNames?.root,
|
|
975
|
+
className
|
|
976
|
+
),
|
|
977
|
+
children: [
|
|
978
|
+
/* @__PURE__ */ jsxs9("div", { className: cn("flex min-w-0 flex-1 items-center gap-3", classNames?.identity), children: [
|
|
979
|
+
onBack ? /* @__PURE__ */ jsx11("button", { type: "button", onClick: onBack, className: "cv-back cv-touch", "aria-label": labels.back, title: labels.back, children: "\u2190" }) : null,
|
|
980
|
+
/* @__PURE__ */ jsx11(Avatar, { name: conversation.clientName, size: "md" }),
|
|
981
|
+
/* @__PURE__ */ jsxs9("div", { className: "min-w-0", children: [
|
|
982
|
+
/* @__PURE__ */ jsx11("p", { className: cn("truncate font-medium", classNames?.name), children: conversation.clientName ?? displayHandle }),
|
|
983
|
+
/* @__PURE__ */ jsxs9("p", { className: cn("truncate text-xs text-gray-500", classNames?.meta), children: [
|
|
984
|
+
flag ? `${flag} ` : "",
|
|
985
|
+
displayHandle,
|
|
986
|
+
/* @__PURE__ */ jsxs9("span", { className: "ml-2 inline-flex items-center gap-1", children: [
|
|
987
|
+
/* @__PURE__ */ jsx11(ChannelIcon, { channel: conversation.channel }),
|
|
988
|
+
/* @__PURE__ */ jsx11("span", { className: "cv-only-wide", children: capabilities.label })
|
|
989
|
+
] }),
|
|
990
|
+
/* @__PURE__ */ jsx11("span", { className: "ml-2 cv-only-wide", children: isHuman ? labels.humanMode : labels.botMode }),
|
|
991
|
+
/* @__PURE__ */ jsx11("span", { className: "ml-1 cv-only-narrow", title: isHuman ? labels.humanMode : labels.botMode, children: isHuman ? "\u{1F9D1}\u200D\u{1F4BC}" : "\u{1F916}" })
|
|
992
|
+
] })
|
|
993
|
+
] })
|
|
994
|
+
] }),
|
|
995
|
+
/* @__PURE__ */ jsxs9("div", { className: cn("flex shrink-0 items-center gap-2", classNames?.actions), children: [
|
|
996
|
+
/* @__PURE__ */ jsxs9("div", { className: cn("hidden items-center gap-2 lg:flex", classNames?.desktopActions), children: [
|
|
997
|
+
utilities.map((utility) => /* @__PURE__ */ jsx11(
|
|
998
|
+
"button",
|
|
999
|
+
{
|
|
1000
|
+
type: "button",
|
|
1001
|
+
onClick: utility.run,
|
|
1002
|
+
disabled: busy,
|
|
1003
|
+
"aria-pressed": utility.active,
|
|
1004
|
+
title: utility.label,
|
|
1005
|
+
"aria-label": utility.label,
|
|
1006
|
+
className: `cv-header-icon ${utility.active ? "cv-header-icon--active" : ""}`,
|
|
1007
|
+
children: utility.icon
|
|
1008
|
+
},
|
|
1009
|
+
utility.key
|
|
1010
|
+
)),
|
|
1011
|
+
actions.map((action) => /* @__PURE__ */ jsx11(
|
|
1012
|
+
"button",
|
|
1013
|
+
{
|
|
1014
|
+
type: "button",
|
|
1015
|
+
onClick: action.run,
|
|
1016
|
+
disabled: busy,
|
|
1017
|
+
className: `cv-header-action ${action.className}`,
|
|
1018
|
+
children: action.label
|
|
1019
|
+
},
|
|
1020
|
+
action.key
|
|
1021
|
+
))
|
|
1022
|
+
] }),
|
|
1023
|
+
menuItems.length > 0 ? /* @__PURE__ */ jsxs9("div", { className: cn("cv-menu lg:hidden", classNames?.mobileMenu), children: [
|
|
1024
|
+
/* @__PURE__ */ jsx11(
|
|
1025
|
+
"button",
|
|
1026
|
+
{
|
|
1027
|
+
type: "button",
|
|
1028
|
+
onClick: () => setMenuOpen(!menuOpen),
|
|
1029
|
+
"aria-expanded": menuOpen,
|
|
1030
|
+
"aria-label": labels.moreActions,
|
|
1031
|
+
title: labels.moreActions,
|
|
1032
|
+
className: "cv-header-icon cv-touch",
|
|
1033
|
+
children: "\u22EE"
|
|
1034
|
+
}
|
|
1035
|
+
),
|
|
1036
|
+
menuOpen ? /* @__PURE__ */ jsx11("div", { className: "cv-menu-panel", role: "menu", children: menuItems.map((item) => /* @__PURE__ */ jsx11(
|
|
1037
|
+
"button",
|
|
1038
|
+
{
|
|
1039
|
+
type: "button",
|
|
1040
|
+
role: "menuitem",
|
|
1041
|
+
onClick: () => {
|
|
1042
|
+
setMenuOpen(false);
|
|
1043
|
+
item.run();
|
|
1044
|
+
},
|
|
1045
|
+
disabled: busy,
|
|
1046
|
+
className: `cv-header-action cv-touch ${item.className}`,
|
|
1047
|
+
children: item.label
|
|
1048
|
+
},
|
|
1049
|
+
item.key
|
|
1050
|
+
)) }) : null
|
|
1051
|
+
] }) : null
|
|
1052
|
+
] })
|
|
1053
|
+
]
|
|
1054
|
+
}
|
|
1055
|
+
);
|
|
1252
1056
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1057
|
+
|
|
1058
|
+
// src/ConversationContextPanel.tsx
|
|
1059
|
+
import { useState as useState6 } from "react";
|
|
1060
|
+
|
|
1061
|
+
// src/useIsNarrow.ts
|
|
1062
|
+
import { useEffect as useEffect3, useState as useState5 } from "react";
|
|
1063
|
+
var NARROW_MAX_WIDTH_PX = 1023;
|
|
1064
|
+
function useIsNarrow() {
|
|
1065
|
+
const [isNarrow, setIsNarrow] = useState5(
|
|
1066
|
+
() => typeof window !== "undefined" && window.matchMedia(`(max-width: ${NARROW_MAX_WIDTH_PX}px)`).matches
|
|
1067
|
+
);
|
|
1068
|
+
useEffect3(() => {
|
|
1069
|
+
const query = window.matchMedia(`(max-width: ${NARROW_MAX_WIDTH_PX}px)`);
|
|
1070
|
+
const handleChange = (event) => setIsNarrow(event.matches);
|
|
1071
|
+
query.addEventListener("change", handleChange);
|
|
1072
|
+
setIsNarrow(query.matches);
|
|
1073
|
+
return () => query.removeEventListener("change", handleChange);
|
|
1074
|
+
}, []);
|
|
1075
|
+
return isNarrow;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/ConversationContextPanel.tsx
|
|
1079
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1080
|
+
var DEFAULT_CONVERSATION_CONTEXT_LABELS = {
|
|
1081
|
+
title: "\u{1F4CB} Suas Sele\xE7\xF5es",
|
|
1082
|
+
empty: "Nada coletado ainda nesta conversa."
|
|
1083
|
+
};
|
|
1084
|
+
function ConversationContextPanel({
|
|
1085
|
+
entries,
|
|
1086
|
+
labels: labelsOverride,
|
|
1087
|
+
className,
|
|
1088
|
+
classNames
|
|
1089
|
+
}) {
|
|
1090
|
+
const labels = { ...DEFAULT_CONVERSATION_CONTEXT_LABELS, ...labelsOverride };
|
|
1091
|
+
const filled = entries.filter((entry) => Boolean(entry.value));
|
|
1092
|
+
const isNarrow = useIsNarrow();
|
|
1093
|
+
const [manualOpen, setManualOpen] = useState6(void 0);
|
|
1094
|
+
const open = manualOpen ?? (!isNarrow && filled.length > 0);
|
|
1095
|
+
return /* @__PURE__ */ jsxs10("section", { className: cn("border-b", classNames?.root, className), children: [
|
|
1096
|
+
/* @__PURE__ */ jsxs10(
|
|
1097
|
+
"button",
|
|
1098
|
+
{
|
|
1099
|
+
type: "button",
|
|
1100
|
+
onClick: () => setManualOpen(!open),
|
|
1101
|
+
"aria-expanded": open,
|
|
1102
|
+
className: cn("flex w-full items-center gap-2 px-4 py-3 text-left text-sm font-medium", classNames?.toggle),
|
|
1103
|
+
children: [
|
|
1104
|
+
/* @__PURE__ */ jsx12("span", { "aria-hidden": true, className: "text-xs", children: open ? "\u25BE" : "\u25B8" }),
|
|
1105
|
+
/* @__PURE__ */ jsx12("span", { children: labels.title }),
|
|
1106
|
+
/* @__PURE__ */ jsxs10("span", { className: cn("rounded-full bg-gray-200 px-2 text-xs dark:bg-gray-700", classNames?.counter), children: [
|
|
1107
|
+
filled.length,
|
|
1108
|
+
"/",
|
|
1109
|
+
entries.length
|
|
1110
|
+
] })
|
|
1111
|
+
]
|
|
1112
|
+
}
|
|
1113
|
+
),
|
|
1114
|
+
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: [
|
|
1115
|
+
/* @__PURE__ */ jsxs10("dt", { className: "truncate text-gray-500", children: [
|
|
1116
|
+
entry.icon ? `${entry.icon} ` : "",
|
|
1117
|
+
entry.label
|
|
1118
|
+
] }),
|
|
1119
|
+
/* @__PURE__ */ jsx12("dd", { className: "truncate font-medium", children: entry.value ? /* @__PURE__ */ jsxs10("span", { className: "text-green-700 dark:text-green-400", children: [
|
|
1120
|
+
"\u2713 ",
|
|
1121
|
+
entry.value
|
|
1122
|
+
] }) : "\u2014" })
|
|
1123
|
+
] }, entry.key)) }) }) : null
|
|
1124
|
+
] });
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// src/WindowExpiredNotice.tsx
|
|
1128
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1129
|
+
var DEFAULT_WINDOW_EXPIRED_LABELS = {
|
|
1130
|
+
title: "\u23F0 Janela de 24h expirada \u2014 o WhatsApp n\xE3o aceita mensagens livres.",
|
|
1131
|
+
description: "Envie uma mensagem de template para reabrir a conversa com o cliente.",
|
|
1132
|
+
sendTemplate: "\u{1F4E8} Enviar Template (HSM)"
|
|
1133
|
+
};
|
|
1134
|
+
function WindowExpiredNotice({
|
|
1135
|
+
onSendTemplate,
|
|
1136
|
+
disabled = false,
|
|
1137
|
+
labels: labelsOverride,
|
|
1138
|
+
className
|
|
1139
|
+
}) {
|
|
1140
|
+
const labels = { ...DEFAULT_WINDOW_EXPIRED_LABELS, ...labelsOverride };
|
|
1141
|
+
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: [
|
|
1142
|
+
/* @__PURE__ */ jsx13("p", { className: "font-medium text-yellow-900 dark:text-yellow-200", children: labels.title }),
|
|
1143
|
+
/* @__PURE__ */ jsx13("p", { className: "text-yellow-800 dark:text-yellow-300", children: labels.description }),
|
|
1144
|
+
onSendTemplate ? /* @__PURE__ */ jsx13("button", { type: "button", onClick: onSendTemplate, disabled, className: "cv-header-action mt-2", children: labels.sendTemplate }) : null
|
|
1145
|
+
] });
|
|
1146
|
+
}
|
|
1147
|
+
function isWindowBlocking(window2) {
|
|
1148
|
+
return window2 === CONVERSATION_WINDOW.EXPIRED;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// src/conversationTranscript.ts
|
|
1152
|
+
var SENDER_LABEL = {
|
|
1153
|
+
customer: "Cliente",
|
|
1154
|
+
bot: "Bot",
|
|
1155
|
+
agent: "Atendente"
|
|
1156
|
+
};
|
|
1157
|
+
function buildTranscriptText(params) {
|
|
1158
|
+
const header = [
|
|
1159
|
+
`Conversa: ${params.clientName ?? params.whatsappNumber}`,
|
|
1160
|
+
`N\xFAmero: ${params.whatsappNumber}`,
|
|
1161
|
+
`Mensagens: ${params.messages.length}`,
|
|
1162
|
+
""
|
|
1163
|
+
];
|
|
1164
|
+
const lines = params.messages.map((message) => {
|
|
1165
|
+
const stamp = new Date(message.timestamp).toLocaleString("pt-BR");
|
|
1166
|
+
const author = message.agentName ?? SENDER_LABEL[message.sender];
|
|
1167
|
+
const body = message.content ?? message.caption ?? `<${message.type}>`;
|
|
1168
|
+
return `[${stamp}] ${author}: ${body}`;
|
|
1169
|
+
});
|
|
1170
|
+
return [...header, ...lines].join("\n");
|
|
1171
|
+
}
|
|
1172
|
+
function buildTranscriptFilename(whatsappNumber, generatedAt) {
|
|
1173
|
+
const stamp = generatedAt.toISOString().slice(0, 10);
|
|
1174
|
+
return `conversa-${whatsappNumber}-${stamp}.txt`;
|
|
1175
|
+
}
|
|
1176
|
+
function downloadTextFile(filename, content) {
|
|
1177
|
+
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
|
1178
|
+
const url = URL.createObjectURL(blob);
|
|
1179
|
+
const anchor = document.createElement("a");
|
|
1180
|
+
anchor.href = url;
|
|
1181
|
+
anchor.download = filename;
|
|
1182
|
+
anchor.click();
|
|
1183
|
+
URL.revokeObjectURL(url);
|
|
1255
1184
|
}
|
|
1256
1185
|
|
|
1257
1186
|
// src/useWaitingNotifications.ts
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1187
|
+
import { useState as useState7, useEffect as useEffect4, useRef as useRef3, useCallback as useCallback3 } from "react";
|
|
1188
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e4;
|
|
1189
|
+
var DEFAULT_LIMIT = 50;
|
|
1190
|
+
var DEFAULT_LABELS = {
|
|
1191
|
+
title: (conversation) => conversation.clientName ?? conversation.whatsappNumber,
|
|
1192
|
+
body: (conversation) => conversation.lastContent ?? "Nova mensagem"
|
|
1193
|
+
};
|
|
1194
|
+
function useWaitingNotifications(params) {
|
|
1195
|
+
const conversationsContext = useConversations();
|
|
1196
|
+
const [conversations, setConversations] = useState7([]);
|
|
1197
|
+
const previousUnreadMap = useRef3(/* @__PURE__ */ new Map());
|
|
1198
|
+
const notifiedIds = useRef3(/* @__PURE__ */ new Set());
|
|
1199
|
+
const isPollingRef = useRef3(false);
|
|
1200
|
+
const isEnabled = params?.enabled ?? true;
|
|
1201
|
+
const intervalMs = params?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1202
|
+
const icon = params?.icon;
|
|
1203
|
+
const listParams = params?.params;
|
|
1204
|
+
const labelTitle = params?.labels?.title ?? DEFAULT_LABELS.title;
|
|
1205
|
+
const labelBody = params?.labels?.body ?? DEFAULT_LABELS.body;
|
|
1206
|
+
const refresh = useCallback3(async () => {
|
|
1207
|
+
if (!conversationsContext || isPollingRef.current) return;
|
|
1208
|
+
isPollingRef.current = true;
|
|
1265
1209
|
try {
|
|
1266
|
-
const result =
|
|
1210
|
+
const result = conversationsOf(
|
|
1211
|
+
await conversationsContext.api.fetchConversations({ limit: DEFAULT_LIMIT, ...listParams })
|
|
1212
|
+
);
|
|
1267
1213
|
setConversations(result);
|
|
1268
1214
|
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
|
-
}
|
|
1215
|
+
for (const conversation of result) currentMap.set(conversation.id, conversation.unread);
|
|
1216
|
+
for (const conversation of result) {
|
|
1217
|
+
if (conversation.unread <= 0) continue;
|
|
1218
|
+
const previousUnread = previousUnreadMap.current.get(conversation.id) ?? 0;
|
|
1219
|
+
if (conversation.unread <= previousUnread || notifiedIds.current.has(conversation.id)) continue;
|
|
1220
|
+
notifiedIds.current.add(conversation.id);
|
|
1221
|
+
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "granted") {
|
|
1222
|
+
new Notification(labelTitle(conversation), { body: labelBody(conversation), ...icon ? { icon } : {} });
|
|
1282
1223
|
}
|
|
1283
1224
|
}
|
|
1284
1225
|
previousUnreadMap.current = currentMap;
|
|
1285
1226
|
} catch {
|
|
1227
|
+
} finally {
|
|
1228
|
+
isPollingRef.current = false;
|
|
1286
1229
|
}
|
|
1287
|
-
}, [
|
|
1230
|
+
}, [conversationsContext, listParams, icon, labelTitle, labelBody]);
|
|
1288
1231
|
useEffect4(() => {
|
|
1232
|
+
if (!isEnabled) return;
|
|
1289
1233
|
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "default") {
|
|
1290
1234
|
Notification.requestPermission();
|
|
1291
1235
|
}
|
|
1292
|
-
|
|
1293
|
-
const interval = setInterval(
|
|
1236
|
+
void refresh();
|
|
1237
|
+
const interval = setInterval(() => void refresh(), intervalMs);
|
|
1294
1238
|
return () => clearInterval(interval);
|
|
1295
|
-
}, [
|
|
1296
|
-
const unreadCount = conversations.reduce((sum,
|
|
1297
|
-
return { unreadCount, conversations };
|
|
1239
|
+
}, [refresh, isEnabled, intervalMs]);
|
|
1240
|
+
const unreadCount = conversations.reduce((sum, conversation) => sum + conversation.unread, 0);
|
|
1241
|
+
return { unreadCount, conversations, refresh };
|
|
1298
1242
|
}
|
|
1299
1243
|
|
|
1300
1244
|
// src/settings/WhatsAppTemplateSettingsForm.tsx
|
|
1301
1245
|
import { Plus, RefreshCw, Save, Trash2 } from "lucide-react";
|
|
1302
|
-
import { jsx as
|
|
1303
|
-
var
|
|
1246
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1247
|
+
var DEFAULT_LABELS2 = {
|
|
1304
1248
|
sectionTitle: "WhatsApp",
|
|
1305
1249
|
sectionDescription: "Template usado para reabrir a janela de 24h com o cliente.",
|
|
1306
1250
|
templateLabel: "Template aprovado",
|
|
@@ -1334,7 +1278,7 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1334
1278
|
onSave,
|
|
1335
1279
|
labels: labelsOverride
|
|
1336
1280
|
}) {
|
|
1337
|
-
const labels = { ...
|
|
1281
|
+
const labels = { ...DEFAULT_LABELS2, ...labelsOverride };
|
|
1338
1282
|
const selected = templates.find((template) => template.name === selectedTemplateName);
|
|
1339
1283
|
function handleSelect(name) {
|
|
1340
1284
|
const template = templates.find((t) => t.name === name);
|
|
@@ -1358,16 +1302,16 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1358
1302
|
}
|
|
1359
1303
|
onVariablesChange([...variables, token]);
|
|
1360
1304
|
}
|
|
1361
|
-
return /* @__PURE__ */
|
|
1362
|
-
/* @__PURE__ */
|
|
1363
|
-
/* @__PURE__ */
|
|
1364
|
-
/* @__PURE__ */
|
|
1305
|
+
return /* @__PURE__ */ jsxs12("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1306
|
+
/* @__PURE__ */ jsxs12("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1307
|
+
/* @__PURE__ */ jsx14("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }),
|
|
1308
|
+
/* @__PURE__ */ jsx14("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1365
1309
|
] }),
|
|
1366
|
-
/* @__PURE__ */
|
|
1367
|
-
/* @__PURE__ */
|
|
1368
|
-
/* @__PURE__ */
|
|
1369
|
-
/* @__PURE__ */
|
|
1370
|
-
/* @__PURE__ */
|
|
1310
|
+
/* @__PURE__ */ jsxs12("form", { onSubmit: onSave, className: "p-6 space-y-4", children: [
|
|
1311
|
+
/* @__PURE__ */ jsxs12("div", { children: [
|
|
1312
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between mb-1.5", children: [
|
|
1313
|
+
/* @__PURE__ */ jsx14("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.templateLabel }),
|
|
1314
|
+
/* @__PURE__ */ jsxs12(
|
|
1371
1315
|
"button",
|
|
1372
1316
|
{
|
|
1373
1317
|
type: "button",
|
|
@@ -1375,31 +1319,31 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1375
1319
|
disabled: loadingTemplates,
|
|
1376
1320
|
className: "flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-50",
|
|
1377
1321
|
children: [
|
|
1378
|
-
/* @__PURE__ */
|
|
1322
|
+
/* @__PURE__ */ jsx14(RefreshCw, { size: 11, className: loadingTemplates ? "animate-spin" : "" }),
|
|
1379
1323
|
labels.refreshList
|
|
1380
1324
|
]
|
|
1381
1325
|
}
|
|
1382
1326
|
)
|
|
1383
1327
|
] }),
|
|
1384
|
-
templatesError && /* @__PURE__ */
|
|
1328
|
+
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
1329
|
labels.errorLoading,
|
|
1386
|
-
/* @__PURE__ */
|
|
1387
|
-
/* @__PURE__ */
|
|
1330
|
+
/* @__PURE__ */ jsx14("br", {}),
|
|
1331
|
+
/* @__PURE__ */ jsxs12("span", { className: "text-gray-500 dark:text-gray-400 mt-0.5 block", children: [
|
|
1388
1332
|
labels.currentNameSaved,
|
|
1389
1333
|
" ",
|
|
1390
|
-
/* @__PURE__ */
|
|
1334
|
+
/* @__PURE__ */ jsx14("strong", { children: selectedTemplateName || "\u2014" })
|
|
1391
1335
|
] })
|
|
1392
1336
|
] }),
|
|
1393
|
-
loadingTemplates && /* @__PURE__ */
|
|
1394
|
-
!loadingTemplates && !templatesError && /* @__PURE__ */
|
|
1337
|
+
loadingTemplates && /* @__PURE__ */ jsx14("div", { className: "h-10 rounded-xl bg-gray-100 dark:bg-gray-700 animate-pulse" }),
|
|
1338
|
+
!loadingTemplates && !templatesError && /* @__PURE__ */ jsxs12(
|
|
1395
1339
|
"select",
|
|
1396
1340
|
{
|
|
1397
1341
|
value: selectedTemplateName,
|
|
1398
1342
|
onChange: (e) => handleSelect(e.target.value),
|
|
1399
1343
|
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
1344
|
children: [
|
|
1401
|
-
/* @__PURE__ */
|
|
1402
|
-
templates.map((template) => /* @__PURE__ */
|
|
1345
|
+
/* @__PURE__ */ jsx14("option", { value: "", children: labels.selectPlaceholder }),
|
|
1346
|
+
templates.map((template) => /* @__PURE__ */ jsxs12("option", { value: template.name, disabled: template.status !== "APPROVED", children: [
|
|
1403
1347
|
template.displayName,
|
|
1404
1348
|
" \xB7 ",
|
|
1405
1349
|
template.shortId,
|
|
@@ -1411,27 +1355,27 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1411
1355
|
]
|
|
1412
1356
|
}
|
|
1413
1357
|
),
|
|
1414
|
-
selected?.bodyText && /* @__PURE__ */
|
|
1358
|
+
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
1359
|
] }),
|
|
1416
|
-
/* @__PURE__ */
|
|
1417
|
-
/* @__PURE__ */
|
|
1418
|
-
/* @__PURE__ */
|
|
1419
|
-
/* @__PURE__ */
|
|
1360
|
+
/* @__PURE__ */ jsxs12("div", { children: [
|
|
1361
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between mb-1.5", children: [
|
|
1362
|
+
/* @__PURE__ */ jsx14("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.variablesLabel }),
|
|
1363
|
+
/* @__PURE__ */ jsxs12(
|
|
1420
1364
|
"button",
|
|
1421
1365
|
{
|
|
1422
1366
|
type: "button",
|
|
1423
1367
|
onClick: () => onVariablesChange([...variables, ""]),
|
|
1424
1368
|
className: "flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline",
|
|
1425
1369
|
children: [
|
|
1426
|
-
/* @__PURE__ */
|
|
1370
|
+
/* @__PURE__ */ jsx14(Plus, { size: 12 }),
|
|
1427
1371
|
labels.addVariable
|
|
1428
1372
|
]
|
|
1429
1373
|
}
|
|
1430
1374
|
)
|
|
1431
1375
|
] }),
|
|
1432
|
-
availableVariables.length > 0 && /* @__PURE__ */
|
|
1433
|
-
/* @__PURE__ */
|
|
1434
|
-
/* @__PURE__ */
|
|
1376
|
+
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: [
|
|
1377
|
+
/* @__PURE__ */ jsx14("p", { className: "text-xs font-medium text-gray-600 dark:text-gray-300 mb-1.5", children: labels.variablesAvailableTitle }),
|
|
1378
|
+
/* @__PURE__ */ jsx14("div", { className: "flex flex-wrap gap-1.5", children: availableVariables.map((available) => /* @__PURE__ */ jsxs12(
|
|
1435
1379
|
"button",
|
|
1436
1380
|
{
|
|
1437
1381
|
type: "button",
|
|
@@ -1439,8 +1383,8 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1439
1383
|
title: `Inserir ${available.token}`,
|
|
1440
1384
|
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
1385
|
children: [
|
|
1442
|
-
/* @__PURE__ */
|
|
1443
|
-
/* @__PURE__ */
|
|
1386
|
+
/* @__PURE__ */ jsx14("code", { className: "text-blue-600 dark:text-blue-300", children: available.token }),
|
|
1387
|
+
/* @__PURE__ */ jsxs12("span", { className: "text-gray-400 dark:text-gray-500", children: [
|
|
1444
1388
|
"\xB7 ",
|
|
1445
1389
|
available.label
|
|
1446
1390
|
] })
|
|
@@ -1448,13 +1392,13 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1448
1392
|
},
|
|
1449
1393
|
available.token
|
|
1450
1394
|
)) }),
|
|
1451
|
-
/* @__PURE__ */
|
|
1395
|
+
/* @__PURE__ */ jsx14("p", { className: "mt-1.5 text-[11px] text-gray-400 dark:text-gray-500", children: labels.variablesAvailableHint })
|
|
1452
1396
|
] }),
|
|
1453
|
-
/* @__PURE__ */
|
|
1454
|
-
variables.length === 0 && /* @__PURE__ */
|
|
1455
|
-
/* @__PURE__ */
|
|
1456
|
-
/* @__PURE__ */
|
|
1457
|
-
/* @__PURE__ */
|
|
1397
|
+
/* @__PURE__ */ jsx14("p", { className: "text-xs text-gray-400 dark:text-gray-500 mb-2", children: labels.variablesMappingHint }),
|
|
1398
|
+
variables.length === 0 && /* @__PURE__ */ jsx14("p", { className: "text-xs text-gray-400 dark:text-gray-500 italic", children: labels.noVariables }),
|
|
1399
|
+
/* @__PURE__ */ jsx14("div", { className: "space-y-2", children: variables.map((variable, index) => /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
1400
|
+
/* @__PURE__ */ jsx14("span", { className: "text-xs text-gray-400 dark:text-gray-500 w-6 text-right flex-shrink-0", children: `{{${index + 1}}}` }),
|
|
1401
|
+
/* @__PURE__ */ jsx14(
|
|
1458
1402
|
"input",
|
|
1459
1403
|
{
|
|
1460
1404
|
type: "text",
|
|
@@ -1464,27 +1408,27 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1464
1408
|
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
1409
|
}
|
|
1466
1410
|
),
|
|
1467
|
-
/* @__PURE__ */
|
|
1411
|
+
/* @__PURE__ */ jsx14(
|
|
1468
1412
|
"button",
|
|
1469
1413
|
{
|
|
1470
1414
|
type: "button",
|
|
1471
1415
|
onClick: () => removeVariable(index),
|
|
1472
1416
|
className: "text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors flex-shrink-0",
|
|
1473
1417
|
title: labels.removeVariable,
|
|
1474
|
-
children: /* @__PURE__ */
|
|
1418
|
+
children: /* @__PURE__ */ jsx14(Trash2, { size: 14 })
|
|
1475
1419
|
}
|
|
1476
1420
|
)
|
|
1477
1421
|
] }, index)) })
|
|
1478
1422
|
] }),
|
|
1479
|
-
saveSuccess && /* @__PURE__ */
|
|
1480
|
-
/* @__PURE__ */
|
|
1423
|
+
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 }),
|
|
1424
|
+
/* @__PURE__ */ jsx14("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs12(
|
|
1481
1425
|
"button",
|
|
1482
1426
|
{
|
|
1483
1427
|
type: "submit",
|
|
1484
1428
|
disabled: saving || !selectedTemplateName.trim(),
|
|
1485
1429
|
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
1430
|
children: [
|
|
1487
|
-
saving ? /* @__PURE__ */
|
|
1431
|
+
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
1432
|
saving ? labels.saving : labels.save
|
|
1489
1433
|
]
|
|
1490
1434
|
}
|
|
@@ -1495,12 +1439,13 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1495
1439
|
|
|
1496
1440
|
// src/settings/WhatsAppCreateTemplateForm.tsx
|
|
1497
1441
|
import { SendHorizonal } from "lucide-react";
|
|
1498
|
-
import { jsx as
|
|
1499
|
-
var
|
|
1442
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1443
|
+
var DEFAULT_LABELS3 = {
|
|
1500
1444
|
sectionTitle: "Criar novo template",
|
|
1501
1445
|
sectionDescription: "Envia um template para aprova\xE7\xE3o da Meta.",
|
|
1502
1446
|
nameLabel: "Nome do template",
|
|
1503
1447
|
nameHint: "Somente letras min\xFAsculas, n\xFAmeros e underscore.",
|
|
1448
|
+
namePlaceholder: "reengajamento_cliente",
|
|
1504
1449
|
categoryLabel: "Categoria",
|
|
1505
1450
|
languageLabel: "Idioma",
|
|
1506
1451
|
headerLabel: "Cabe\xE7alho",
|
|
@@ -1546,82 +1491,82 @@ function WhatsAppCreateTemplateForm({
|
|
|
1546
1491
|
labels: labelsOverride,
|
|
1547
1492
|
variableExamples = DEFAULT_VARIABLE_EXAMPLES
|
|
1548
1493
|
}) {
|
|
1549
|
-
const labels = { ...
|
|
1494
|
+
const labels = { ...DEFAULT_LABELS3, ...labelsOverride };
|
|
1550
1495
|
const detectedVariables = detectVariables(value.bodyText);
|
|
1551
1496
|
function set(key, next) {
|
|
1552
1497
|
onChange({ ...value, [key]: next });
|
|
1553
1498
|
}
|
|
1554
|
-
return /* @__PURE__ */
|
|
1555
|
-
/* @__PURE__ */
|
|
1556
|
-
/* @__PURE__ */
|
|
1557
|
-
/* @__PURE__ */
|
|
1499
|
+
return /* @__PURE__ */ jsxs13("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1500
|
+
/* @__PURE__ */ jsxs13("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1501
|
+
/* @__PURE__ */ jsx15("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }),
|
|
1502
|
+
/* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1558
1503
|
] }),
|
|
1559
|
-
/* @__PURE__ */
|
|
1560
|
-
/* @__PURE__ */
|
|
1561
|
-
/* @__PURE__ */
|
|
1562
|
-
/* @__PURE__ */
|
|
1563
|
-
/* @__PURE__ */
|
|
1504
|
+
/* @__PURE__ */ jsxs13("form", { onSubmit, className: "p-6 space-y-4", children: [
|
|
1505
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1506
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.nameLabel }),
|
|
1507
|
+
/* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.nameHint }),
|
|
1508
|
+
/* @__PURE__ */ jsx15(
|
|
1564
1509
|
"input",
|
|
1565
1510
|
{
|
|
1566
1511
|
type: "text",
|
|
1567
1512
|
value: value.name,
|
|
1568
1513
|
onChange: (e) => set("name", e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, "_")),
|
|
1569
|
-
placeholder:
|
|
1514
|
+
placeholder: labels.namePlaceholder,
|
|
1570
1515
|
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
1516
|
required: true
|
|
1572
1517
|
}
|
|
1573
1518
|
)
|
|
1574
1519
|
] }),
|
|
1575
|
-
/* @__PURE__ */
|
|
1576
|
-
/* @__PURE__ */
|
|
1577
|
-
/* @__PURE__ */
|
|
1578
|
-
/* @__PURE__ */
|
|
1520
|
+
/* @__PURE__ */ jsxs13("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
1521
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1522
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.categoryLabel }),
|
|
1523
|
+
/* @__PURE__ */ jsxs13(
|
|
1579
1524
|
"select",
|
|
1580
1525
|
{
|
|
1581
1526
|
value: value.category,
|
|
1582
1527
|
onChange: (e) => set("category", e.target.value),
|
|
1583
1528
|
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
1529
|
children: [
|
|
1585
|
-
/* @__PURE__ */
|
|
1586
|
-
/* @__PURE__ */
|
|
1530
|
+
/* @__PURE__ */ jsx15("option", { value: "UTILITY", children: "UTILITY (Transacional)" }),
|
|
1531
|
+
/* @__PURE__ */ jsx15("option", { value: "MARKETING", children: "MARKETING (Promocional)" })
|
|
1587
1532
|
]
|
|
1588
1533
|
}
|
|
1589
1534
|
)
|
|
1590
1535
|
] }),
|
|
1591
|
-
/* @__PURE__ */
|
|
1592
|
-
/* @__PURE__ */
|
|
1593
|
-
/* @__PURE__ */
|
|
1536
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1537
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.languageLabel }),
|
|
1538
|
+
/* @__PURE__ */ jsxs13(
|
|
1594
1539
|
"select",
|
|
1595
1540
|
{
|
|
1596
1541
|
value: value.language,
|
|
1597
1542
|
onChange: (e) => set("language", e.target.value),
|
|
1598
1543
|
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
1544
|
children: [
|
|
1600
|
-
/* @__PURE__ */
|
|
1601
|
-
/* @__PURE__ */
|
|
1602
|
-
/* @__PURE__ */
|
|
1545
|
+
/* @__PURE__ */ jsx15("option", { value: "pt_BR", children: "Portugu\xEAs (Brasil)" }),
|
|
1546
|
+
/* @__PURE__ */ jsx15("option", { value: "en_US", children: "Ingl\xEAs (EUA)" }),
|
|
1547
|
+
/* @__PURE__ */ jsx15("option", { value: "es_ES", children: "Espanhol" })
|
|
1603
1548
|
]
|
|
1604
1549
|
}
|
|
1605
1550
|
)
|
|
1606
1551
|
] })
|
|
1607
1552
|
] }),
|
|
1608
|
-
/* @__PURE__ */
|
|
1609
|
-
/* @__PURE__ */
|
|
1610
|
-
/* @__PURE__ */
|
|
1611
|
-
/* @__PURE__ */
|
|
1612
|
-
/* @__PURE__ */
|
|
1553
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1554
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.headerLabel }),
|
|
1555
|
+
/* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.headerHint }),
|
|
1556
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex gap-2", children: [
|
|
1557
|
+
/* @__PURE__ */ jsxs13(
|
|
1613
1558
|
"select",
|
|
1614
1559
|
{
|
|
1615
1560
|
value: value.headerType,
|
|
1616
1561
|
onChange: (e) => set("headerType", e.target.value),
|
|
1617
1562
|
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
1563
|
children: [
|
|
1619
|
-
/* @__PURE__ */
|
|
1620
|
-
/* @__PURE__ */
|
|
1564
|
+
/* @__PURE__ */ jsx15("option", { value: "NONE", children: labels.headerNone }),
|
|
1565
|
+
/* @__PURE__ */ jsx15("option", { value: "TEXT", children: labels.headerText })
|
|
1621
1566
|
]
|
|
1622
1567
|
}
|
|
1623
1568
|
),
|
|
1624
|
-
value.headerType === "TEXT" && /* @__PURE__ */
|
|
1569
|
+
value.headerType === "TEXT" && /* @__PURE__ */ jsx15(
|
|
1625
1570
|
"input",
|
|
1626
1571
|
{
|
|
1627
1572
|
type: "text",
|
|
@@ -1634,10 +1579,10 @@ function WhatsAppCreateTemplateForm({
|
|
|
1634
1579
|
)
|
|
1635
1580
|
] })
|
|
1636
1581
|
] }),
|
|
1637
|
-
/* @__PURE__ */
|
|
1638
|
-
/* @__PURE__ */
|
|
1639
|
-
/* @__PURE__ */
|
|
1640
|
-
/* @__PURE__ */
|
|
1582
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1583
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.bodyLabel }),
|
|
1584
|
+
/* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.bodyHint }),
|
|
1585
|
+
/* @__PURE__ */ jsx15(
|
|
1641
1586
|
"textarea",
|
|
1642
1587
|
{
|
|
1643
1588
|
value: value.bodyText,
|
|
@@ -1648,26 +1593,26 @@ function WhatsAppCreateTemplateForm({
|
|
|
1648
1593
|
required: true
|
|
1649
1594
|
}
|
|
1650
1595
|
),
|
|
1651
|
-
detectedVariables.length > 0 && /* @__PURE__ */
|
|
1652
|
-
/* @__PURE__ */
|
|
1653
|
-
/* @__PURE__ */
|
|
1654
|
-
/* @__PURE__ */
|
|
1655
|
-
/* @__PURE__ */
|
|
1596
|
+
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: [
|
|
1597
|
+
/* @__PURE__ */ jsx15("p", { className: "font-medium text-gray-500 dark:text-gray-400 mb-1.5 uppercase tracking-wide text-xs", children: labels.detectedVariables }),
|
|
1598
|
+
/* @__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: [
|
|
1599
|
+
/* @__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}}}` }),
|
|
1600
|
+
/* @__PURE__ */ jsxs13("span", { className: "text-gray-400 dark:text-gray-500", children: [
|
|
1656
1601
|
"= ",
|
|
1657
1602
|
variableExamples[Number(num) - 1] ?? labels.variableLabel(Number(num))
|
|
1658
1603
|
] })
|
|
1659
1604
|
] }, num)) }),
|
|
1660
|
-
/* @__PURE__ */
|
|
1605
|
+
/* @__PURE__ */ jsxs13("p", { className: "text-gray-400 dark:text-gray-500 mt-1.5 text-xs", children: [
|
|
1661
1606
|
labels.configureHintPrefix,
|
|
1662
|
-
/* @__PURE__ */
|
|
1607
|
+
/* @__PURE__ */ jsx15("strong", { children: labels.configureHintLink }),
|
|
1663
1608
|
labels.configureHintSuffix
|
|
1664
1609
|
] })
|
|
1665
1610
|
] })
|
|
1666
1611
|
] }),
|
|
1667
|
-
/* @__PURE__ */
|
|
1668
|
-
/* @__PURE__ */
|
|
1669
|
-
/* @__PURE__ */
|
|
1670
|
-
/* @__PURE__ */
|
|
1612
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1613
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.footerLabel }),
|
|
1614
|
+
/* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.footerHint }),
|
|
1615
|
+
/* @__PURE__ */ jsx15(
|
|
1671
1616
|
"input",
|
|
1672
1617
|
{
|
|
1673
1618
|
type: "text",
|
|
@@ -1679,48 +1624,48 @@ function WhatsAppCreateTemplateForm({
|
|
|
1679
1624
|
}
|
|
1680
1625
|
)
|
|
1681
1626
|
] }),
|
|
1682
|
-
/* @__PURE__ */
|
|
1683
|
-
/* @__PURE__ */
|
|
1684
|
-
/* @__PURE__ */
|
|
1685
|
-
/* @__PURE__ */
|
|
1686
|
-
/* @__PURE__ */
|
|
1687
|
-
/* @__PURE__ */
|
|
1688
|
-
/* @__PURE__ */
|
|
1689
|
-
/* @__PURE__ */
|
|
1627
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1628
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block", children: labels.previewLabel }),
|
|
1629
|
+
/* @__PURE__ */ jsxs13("div", { className: "rounded-2xl bg-[#e5ddd5] dark:bg-[#1a1a2e] border border-gray-300 dark:border-gray-600 overflow-hidden shadow-inner", children: [
|
|
1630
|
+
/* @__PURE__ */ jsxs13("div", { className: "bg-[#075e54] px-4 py-2.5 flex items-center gap-2", children: [
|
|
1631
|
+
/* @__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() }),
|
|
1632
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1633
|
+
/* @__PURE__ */ jsx15("p", { className: "text-white text-xs font-semibold leading-tight", children: previewCompanyName }),
|
|
1634
|
+
/* @__PURE__ */ jsx15("p", { className: "text-white/60 text-xs", children: labels.previewOnline })
|
|
1690
1635
|
] })
|
|
1691
1636
|
] }),
|
|
1692
|
-
/* @__PURE__ */
|
|
1693
|
-
/* @__PURE__ */
|
|
1694
|
-
/* @__PURE__ */
|
|
1695
|
-
/* @__PURE__ */
|
|
1637
|
+
/* @__PURE__ */ jsxs13("div", { className: "px-3 py-4 space-y-3", style: { minHeight: "120px" }, children: [
|
|
1638
|
+
/* @__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: [
|
|
1639
|
+
/* @__PURE__ */ jsx15("p", { className: "text-gray-500 dark:text-gray-400 leading-tight text-sm", children: labels.previewDescription }),
|
|
1640
|
+
/* @__PURE__ */ jsx15("p", { className: "text-gray-400 dark:text-gray-500 mt-1 text-right text-[9px]", children: "12:00" })
|
|
1696
1641
|
] }) }),
|
|
1697
|
-
/* @__PURE__ */
|
|
1698
|
-
value.headerType === "TEXT" && value.headerText && /* @__PURE__ */
|
|
1699
|
-
/* @__PURE__ */
|
|
1700
|
-
value.footerText && /* @__PURE__ */
|
|
1701
|
-
/* @__PURE__ */
|
|
1642
|
+
/* @__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: [
|
|
1643
|
+
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 }),
|
|
1644
|
+
/* @__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 }) }),
|
|
1645
|
+
value.footerText && /* @__PURE__ */ jsx15("p", { className: "text-gray-400 dark:text-gray-400 mt-1 text-xs", children: value.footerText }),
|
|
1646
|
+
/* @__PURE__ */ jsx15("p", { className: "text-gray-400 dark:text-gray-400 mt-1 text-right text-[9px]", children: "12:01 \u2713" })
|
|
1702
1647
|
] }) })
|
|
1703
1648
|
] })
|
|
1704
1649
|
] })
|
|
1705
1650
|
] }),
|
|
1706
|
-
result && /* @__PURE__ */
|
|
1651
|
+
result && /* @__PURE__ */ jsxs13(
|
|
1707
1652
|
"div",
|
|
1708
1653
|
{
|
|
1709
1654
|
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
1655
|
children: [
|
|
1711
1656
|
result.message,
|
|
1712
|
-
result.ok && result.status && /* @__PURE__ */
|
|
1657
|
+
result.ok && result.status && /* @__PURE__ */ jsx15("p", { className: "text-xs mt-1 opacity-75", children: labels.successStatus(result.status) })
|
|
1713
1658
|
]
|
|
1714
1659
|
}
|
|
1715
1660
|
),
|
|
1716
|
-
/* @__PURE__ */
|
|
1661
|
+
/* @__PURE__ */ jsx15("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs13(
|
|
1717
1662
|
"button",
|
|
1718
1663
|
{
|
|
1719
1664
|
type: "submit",
|
|
1720
1665
|
disabled: submitting || !value.name || !value.bodyText,
|
|
1721
1666
|
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
1667
|
children: [
|
|
1723
|
-
submitting ? /* @__PURE__ */
|
|
1668
|
+
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
1669
|
submitting ? labels.sending : labels.sendForApproval
|
|
1725
1670
|
]
|
|
1726
1671
|
}
|
|
@@ -1731,8 +1676,8 @@ function WhatsAppCreateTemplateForm({
|
|
|
1731
1676
|
|
|
1732
1677
|
// src/settings/WelcomeFarewellForm.tsx
|
|
1733
1678
|
import { LogOut, Mail, Save as Save2 } from "lucide-react";
|
|
1734
|
-
import { jsx as
|
|
1735
|
-
var
|
|
1679
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1680
|
+
var DEFAULT_LABELS4 = {
|
|
1736
1681
|
sectionTitle: "Boas-vindas e encerramento",
|
|
1737
1682
|
welcomeMessage: "Mensagem de boas-vindas",
|
|
1738
1683
|
welcomeMessagePlaceholder: "Ol\xE1! Sou o assistente virtual da {empresa}...",
|
|
@@ -1758,16 +1703,16 @@ function WelcomeFarewellForm({
|
|
|
1758
1703
|
farewellPlaceholders = DEFAULT_FAREWELL_PLACEHOLDERS,
|
|
1759
1704
|
labels: labelsOverride
|
|
1760
1705
|
}) {
|
|
1761
|
-
const labels = { ...
|
|
1762
|
-
return /* @__PURE__ */
|
|
1763
|
-
/* @__PURE__ */
|
|
1764
|
-
/* @__PURE__ */
|
|
1765
|
-
/* @__PURE__ */
|
|
1766
|
-
/* @__PURE__ */
|
|
1767
|
-
/* @__PURE__ */
|
|
1706
|
+
const labels = { ...DEFAULT_LABELS4, ...labelsOverride };
|
|
1707
|
+
return /* @__PURE__ */ jsxs14("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1708
|
+
/* @__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 }) }),
|
|
1709
|
+
/* @__PURE__ */ jsxs14("form", { onSubmit: onSave, className: "p-6 space-y-5", children: [
|
|
1710
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1711
|
+
/* @__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: [
|
|
1712
|
+
/* @__PURE__ */ jsx16(Mail, { size: 13, className: "text-blue-600 dark:text-blue-400" }),
|
|
1768
1713
|
labels.welcomeMessage
|
|
1769
1714
|
] }),
|
|
1770
|
-
/* @__PURE__ */
|
|
1715
|
+
/* @__PURE__ */ jsx16(
|
|
1771
1716
|
WhatsAppMessageEditor,
|
|
1772
1717
|
{
|
|
1773
1718
|
value: welcomeMessage,
|
|
@@ -1776,14 +1721,14 @@ function WelcomeFarewellForm({
|
|
|
1776
1721
|
placeholders: welcomePlaceholders
|
|
1777
1722
|
}
|
|
1778
1723
|
),
|
|
1779
|
-
/* @__PURE__ */
|
|
1724
|
+
/* @__PURE__ */ jsx16("p", { className: "mt-1.5 text-xs text-gray-500 dark:text-gray-400", children: labels.welcomeMessageHint })
|
|
1780
1725
|
] }),
|
|
1781
|
-
/* @__PURE__ */
|
|
1782
|
-
/* @__PURE__ */
|
|
1783
|
-
/* @__PURE__ */
|
|
1726
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1727
|
+
/* @__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: [
|
|
1728
|
+
/* @__PURE__ */ jsx16(LogOut, { size: 13, className: "text-blue-600 dark:text-blue-400" }),
|
|
1784
1729
|
labels.farewellMessage
|
|
1785
1730
|
] }),
|
|
1786
|
-
/* @__PURE__ */
|
|
1731
|
+
/* @__PURE__ */ jsx16(
|
|
1787
1732
|
WhatsAppMessageEditor,
|
|
1788
1733
|
{
|
|
1789
1734
|
value: farewellMessage,
|
|
@@ -1792,17 +1737,17 @@ function WelcomeFarewellForm({
|
|
|
1792
1737
|
placeholders: farewellPlaceholders
|
|
1793
1738
|
}
|
|
1794
1739
|
),
|
|
1795
|
-
/* @__PURE__ */
|
|
1740
|
+
/* @__PURE__ */ jsx16("p", { className: "mt-1.5 text-xs text-gray-500 dark:text-gray-400", children: labels.farewellMessageHint })
|
|
1796
1741
|
] }),
|
|
1797
|
-
saveSuccess && /* @__PURE__ */
|
|
1798
|
-
/* @__PURE__ */
|
|
1742
|
+
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 }),
|
|
1743
|
+
/* @__PURE__ */ jsx16("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs14(
|
|
1799
1744
|
"button",
|
|
1800
1745
|
{
|
|
1801
1746
|
type: "submit",
|
|
1802
1747
|
disabled: saving,
|
|
1803
1748
|
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
1749
|
children: [
|
|
1805
|
-
saving ? /* @__PURE__ */
|
|
1750
|
+
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
1751
|
saving ? labels.saving : labels.save
|
|
1807
1752
|
]
|
|
1808
1753
|
}
|
|
@@ -1811,10 +1756,57 @@ function WelcomeFarewellForm({
|
|
|
1811
1756
|
] });
|
|
1812
1757
|
}
|
|
1813
1758
|
|
|
1759
|
+
// src/settings/WhatsAppTemplatesSettings.tsx
|
|
1760
|
+
import { useState as useState8 } from "react";
|
|
1761
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1762
|
+
var TEMPLATE_SETTINGS_TAB = {
|
|
1763
|
+
SELECT: "select",
|
|
1764
|
+
CREATE: "create"
|
|
1765
|
+
};
|
|
1766
|
+
var DEFAULT_TEMPLATES_SETTINGS_LABELS = {
|
|
1767
|
+
selectTab: "Template de reengajamento",
|
|
1768
|
+
createTab: "Criar template"
|
|
1769
|
+
};
|
|
1770
|
+
function WhatsAppTemplatesSettings({
|
|
1771
|
+
labels: labelsOverride,
|
|
1772
|
+
create,
|
|
1773
|
+
...settingsProps
|
|
1774
|
+
}) {
|
|
1775
|
+
const labels = { ...DEFAULT_TEMPLATES_SETTINGS_LABELS, ...labelsOverride };
|
|
1776
|
+
const [tab, setTab] = useState8(TEMPLATE_SETTINGS_TAB.SELECT);
|
|
1777
|
+
return /* @__PURE__ */ jsxs15("div", { className: "space-y-4", children: [
|
|
1778
|
+
/* @__PURE__ */ jsxs15("nav", { className: "flex gap-1 border-b", role: "tablist", children: [
|
|
1779
|
+
/* @__PURE__ */ jsx17(
|
|
1780
|
+
"button",
|
|
1781
|
+
{
|
|
1782
|
+
type: "button",
|
|
1783
|
+
role: "tab",
|
|
1784
|
+
"aria-selected": tab === TEMPLATE_SETTINGS_TAB.SELECT,
|
|
1785
|
+
onClick: () => setTab(TEMPLATE_SETTINGS_TAB.SELECT),
|
|
1786
|
+
className: `cv-subtab ${tab === TEMPLATE_SETTINGS_TAB.SELECT ? "cv-subtab--active" : ""}`,
|
|
1787
|
+
children: labels.selectTab
|
|
1788
|
+
}
|
|
1789
|
+
),
|
|
1790
|
+
create ? /* @__PURE__ */ jsx17(
|
|
1791
|
+
"button",
|
|
1792
|
+
{
|
|
1793
|
+
type: "button",
|
|
1794
|
+
role: "tab",
|
|
1795
|
+
"aria-selected": tab === TEMPLATE_SETTINGS_TAB.CREATE,
|
|
1796
|
+
onClick: () => setTab(TEMPLATE_SETTINGS_TAB.CREATE),
|
|
1797
|
+
className: `cv-subtab ${tab === TEMPLATE_SETTINGS_TAB.CREATE ? "cv-subtab--active" : ""}`,
|
|
1798
|
+
children: labels.createTab
|
|
1799
|
+
}
|
|
1800
|
+
) : null
|
|
1801
|
+
] }),
|
|
1802
|
+
tab === TEMPLATE_SETTINGS_TAB.SELECT ? /* @__PURE__ */ jsx17(WhatsAppTemplateSettingsForm, { ...settingsProps }) : create ? /* @__PURE__ */ jsx17(WhatsAppCreateTemplateForm, { ...create }) : null
|
|
1803
|
+
] });
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1814
1806
|
// src/settings/TopicsForm.tsx
|
|
1815
1807
|
import { Megaphone, Save as Save3 } from "lucide-react";
|
|
1816
|
-
import { jsx as
|
|
1817
|
-
var
|
|
1808
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1809
|
+
var DEFAULT_LABELS5 = {
|
|
1818
1810
|
sectionTitle: "T\xF3picos intermedi\xE1rios",
|
|
1819
1811
|
sectionDescription: "Mensagens autom\xE1ticas para assuntos espec\xEDficos, disparadas quando o cliente demonstra interesse.",
|
|
1820
1812
|
messagePlaceholder: "Digite a mensagem autom\xE1tica para este t\xF3pico...",
|
|
@@ -1830,20 +1822,20 @@ function TopicsForm({
|
|
|
1830
1822
|
saveSuccess = false,
|
|
1831
1823
|
labels: labelsOverride
|
|
1832
1824
|
}) {
|
|
1833
|
-
const labels = { ...
|
|
1834
|
-
return /* @__PURE__ */
|
|
1835
|
-
/* @__PURE__ */
|
|
1836
|
-
/* @__PURE__ */
|
|
1837
|
-
/* @__PURE__ */
|
|
1825
|
+
const labels = { ...DEFAULT_LABELS5, ...labelsOverride };
|
|
1826
|
+
return /* @__PURE__ */ jsxs16("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1827
|
+
/* @__PURE__ */ jsxs16("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1828
|
+
/* @__PURE__ */ jsxs16("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
1829
|
+
/* @__PURE__ */ jsx18(Megaphone, { size: 16, className: "text-orange-600 dark:text-orange-400" }),
|
|
1838
1830
|
labels.sectionTitle
|
|
1839
1831
|
] }),
|
|
1840
|
-
/* @__PURE__ */
|
|
1832
|
+
/* @__PURE__ */ jsx18("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1841
1833
|
] }),
|
|
1842
|
-
/* @__PURE__ */
|
|
1843
|
-
topics.map((topic) => /* @__PURE__ */
|
|
1844
|
-
/* @__PURE__ */
|
|
1845
|
-
/* @__PURE__ */
|
|
1846
|
-
/* @__PURE__ */
|
|
1834
|
+
/* @__PURE__ */ jsxs16("form", { onSubmit: onSave, className: "p-6 space-y-6", children: [
|
|
1835
|
+
topics.map((topic) => /* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
1836
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-start gap-4", children: [
|
|
1837
|
+
/* @__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 }) }),
|
|
1838
|
+
/* @__PURE__ */ jsx18(
|
|
1847
1839
|
"button",
|
|
1848
1840
|
{
|
|
1849
1841
|
type: "button",
|
|
@@ -1851,7 +1843,7 @@ function TopicsForm({
|
|
|
1851
1843
|
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
1844
|
role: "switch",
|
|
1853
1845
|
"aria-checked": topic.enabled,
|
|
1854
|
-
children: /* @__PURE__ */
|
|
1846
|
+
children: /* @__PURE__ */ jsx18(
|
|
1855
1847
|
"span",
|
|
1856
1848
|
{
|
|
1857
1849
|
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 +1852,7 @@ function TopicsForm({
|
|
|
1860
1852
|
}
|
|
1861
1853
|
)
|
|
1862
1854
|
] }),
|
|
1863
|
-
/* @__PURE__ */
|
|
1855
|
+
/* @__PURE__ */ jsx18(
|
|
1864
1856
|
"textarea",
|
|
1865
1857
|
{
|
|
1866
1858
|
value: topic.message,
|
|
@@ -1872,55 +1864,27 @@ function TopicsForm({
|
|
|
1872
1864
|
}
|
|
1873
1865
|
)
|
|
1874
1866
|
] }, topic.key)),
|
|
1875
|
-
/* @__PURE__ */
|
|
1876
|
-
/* @__PURE__ */
|
|
1867
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-3", children: [
|
|
1868
|
+
/* @__PURE__ */ jsxs16(
|
|
1877
1869
|
"button",
|
|
1878
1870
|
{
|
|
1879
1871
|
type: "submit",
|
|
1880
1872
|
disabled: saving,
|
|
1881
1873
|
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
1874
|
children: [
|
|
1883
|
-
/* @__PURE__ */
|
|
1875
|
+
/* @__PURE__ */ jsx18(Save3, { size: 14 }),
|
|
1884
1876
|
labels.saveButton
|
|
1885
1877
|
]
|
|
1886
1878
|
}
|
|
1887
1879
|
),
|
|
1888
|
-
saveSuccess && /* @__PURE__ */
|
|
1880
|
+
saveSuccess && /* @__PURE__ */ jsx18("span", { className: "text-sm text-green-600 dark:text-green-400", children: labels.saveSuccess })
|
|
1889
1881
|
] })
|
|
1890
1882
|
] })
|
|
1891
1883
|
] });
|
|
1892
1884
|
}
|
|
1893
1885
|
|
|
1894
1886
|
// 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
|
|
1887
|
+
import { useCallback as useCallback4 } from "react";
|
|
1924
1888
|
function useConversationMessages(conversationId, params) {
|
|
1925
1889
|
const context = useConversations();
|
|
1926
1890
|
if (!context) {
|
|
@@ -1931,7 +1895,7 @@ function useConversationMessages(conversationId, params) {
|
|
|
1931
1895
|
() => api.fetchMessages(conversationId, params),
|
|
1932
1896
|
[conversationId, params?.limit, params?.before]
|
|
1933
1897
|
);
|
|
1934
|
-
const sendMessage =
|
|
1898
|
+
const sendMessage = useCallback4(
|
|
1935
1899
|
async (text) => {
|
|
1936
1900
|
const message = await api.sendMessage(conversationId, text);
|
|
1937
1901
|
await refetch();
|
|
@@ -1939,7 +1903,7 @@ function useConversationMessages(conversationId, params) {
|
|
|
1939
1903
|
},
|
|
1940
1904
|
[api, conversationId, refetch]
|
|
1941
1905
|
);
|
|
1942
|
-
const sendMedia =
|
|
1906
|
+
const sendMedia = useCallback4(
|
|
1943
1907
|
async (mediaData) => {
|
|
1944
1908
|
const message = await api.sendMedia(conversationId, mediaData);
|
|
1945
1909
|
await refetch();
|
|
@@ -1947,14 +1911,14 @@ function useConversationMessages(conversationId, params) {
|
|
|
1947
1911
|
},
|
|
1948
1912
|
[api, conversationId, refetch]
|
|
1949
1913
|
);
|
|
1950
|
-
const sendTemplate =
|
|
1914
|
+
const sendTemplate = useCallback4(
|
|
1951
1915
|
async (templateData) => {
|
|
1952
1916
|
await api.sendTemplate(conversationId, templateData);
|
|
1953
1917
|
await refetch();
|
|
1954
1918
|
},
|
|
1955
1919
|
[api, conversationId, refetch]
|
|
1956
1920
|
);
|
|
1957
|
-
const markRead =
|
|
1921
|
+
const markRead = useCallback4(() => api.markRead(conversationId), [api, conversationId]);
|
|
1958
1922
|
return { messages: data ?? [], loading, error, refetch, sendMessage, sendMedia, sendTemplate, markRead };
|
|
1959
1923
|
}
|
|
1960
1924
|
|
|
@@ -1965,11 +1929,15 @@ function useConversationList(params) {
|
|
|
1965
1929
|
throw new Error("useConversationList requires an ancestor <ConversationsProvider>");
|
|
1966
1930
|
}
|
|
1967
1931
|
const { api } = context;
|
|
1932
|
+
const filtersKey = JSON.stringify(params?.filters ?? {});
|
|
1968
1933
|
const { data, loading, error, refetch } = useAsyncResource(
|
|
1969
1934
|
() => api.fetchConversations(params),
|
|
1970
|
-
[params?.page, params?.limit, params?.waitingHuman, params?.search]
|
|
1935
|
+
[params?.page, params?.limit, params?.waitingHuman, params?.search, filtersKey]
|
|
1971
1936
|
);
|
|
1972
|
-
|
|
1937
|
+
if (data === void 0) {
|
|
1938
|
+
return { conversations: [], total: 0, loading, error, refetch };
|
|
1939
|
+
}
|
|
1940
|
+
return { conversations: conversationsOf(data), total: totalOf(data), loading, error, refetch };
|
|
1973
1941
|
}
|
|
1974
1942
|
|
|
1975
1943
|
// src/hooks/useConversationContext.ts
|
|
@@ -1983,27 +1951,13 @@ function useConversationContext(conversationId) {
|
|
|
1983
1951
|
return { context: data, loading, error, refetch };
|
|
1984
1952
|
}
|
|
1985
1953
|
|
|
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
1954
|
// src/hooks/useConversationRealtime.ts
|
|
2001
|
-
import { useEffect as
|
|
1955
|
+
import { useEffect as useEffect5, useRef as useRef4 } from "react";
|
|
2002
1956
|
function useConversationRealtime(conversationId, onEvent) {
|
|
2003
1957
|
const context = useConversations();
|
|
2004
|
-
const onEventRef =
|
|
1958
|
+
const onEventRef = useRef4(onEvent);
|
|
2005
1959
|
onEventRef.current = onEvent;
|
|
2006
|
-
|
|
1960
|
+
useEffect5(() => {
|
|
2007
1961
|
if (!context || !conversationId) return;
|
|
2008
1962
|
const source = context.sse.connectConversationStream(conversationId);
|
|
2009
1963
|
const handler = (event) => onEventRef.current(event);
|
|
@@ -2016,9 +1970,9 @@ function useConversationRealtime(conversationId, onEvent) {
|
|
|
2016
1970
|
}
|
|
2017
1971
|
function useGlobalRealtime(onEvent) {
|
|
2018
1972
|
const context = useConversations();
|
|
2019
|
-
const onEventRef =
|
|
1973
|
+
const onEventRef = useRef4(onEvent);
|
|
2020
1974
|
onEventRef.current = onEvent;
|
|
2021
|
-
|
|
1975
|
+
useEffect5(() => {
|
|
2022
1976
|
if (!context) return;
|
|
2023
1977
|
const source = context.sse.connectGlobalStream();
|
|
2024
1978
|
const handler = (event) => onEventRef.current(event);
|
|
@@ -2029,16 +1983,78 @@ function useGlobalRealtime(onEvent) {
|
|
|
2029
1983
|
};
|
|
2030
1984
|
}, [context]);
|
|
2031
1985
|
}
|
|
1986
|
+
|
|
1987
|
+
// src/hooks/useConversationActions.ts
|
|
1988
|
+
import { useMemo as useMemo2 } from "react";
|
|
1989
|
+
function useConversationActions(conversationId) {
|
|
1990
|
+
const context = useConversations();
|
|
1991
|
+
if (!context) {
|
|
1992
|
+
throw new Error("useConversationActions requires an ancestor <ConversationsProvider>");
|
|
1993
|
+
}
|
|
1994
|
+
const { api } = context;
|
|
1995
|
+
return useMemo2(
|
|
1996
|
+
() => ({
|
|
1997
|
+
takeover: api.takeover ? () => api.takeover(conversationId) : void 0,
|
|
1998
|
+
release: api.release ? () => api.release(conversationId) : void 0,
|
|
1999
|
+
finalize: api.finalize ? () => api.finalize(conversationId) : void 0
|
|
2000
|
+
}),
|
|
2001
|
+
[api, conversationId]
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
function useInboxActions() {
|
|
2005
|
+
const context = useConversations();
|
|
2006
|
+
if (!context) {
|
|
2007
|
+
throw new Error("useInboxActions requires an ancestor <ConversationsProvider>");
|
|
2008
|
+
}
|
|
2009
|
+
const { api } = context;
|
|
2010
|
+
return useMemo2(
|
|
2011
|
+
() => ({
|
|
2012
|
+
markAllRead: api.markAllRead ? () => api.markAllRead() : void 0,
|
|
2013
|
+
listTemplates: api.listTemplates ? () => api.listTemplates() : void 0
|
|
2014
|
+
}),
|
|
2015
|
+
[api]
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2032
2018
|
export {
|
|
2033
2019
|
AudioPlayer,
|
|
2034
2020
|
Avatar,
|
|
2021
|
+
CHANNEL_BRAND_COLOR,
|
|
2022
|
+
CHANNEL_CAPABILITIES,
|
|
2023
|
+
CHANNEL_FILTER_ALL,
|
|
2024
|
+
CONVERSATION_CHANNEL,
|
|
2025
|
+
CONVERSATION_WINDOW,
|
|
2026
|
+
ChannelIcon,
|
|
2027
|
+
ConversationContextPanel,
|
|
2028
|
+
ConversationDocumentsPanel,
|
|
2029
|
+
ConversationHeader,
|
|
2035
2030
|
ConversationListItem,
|
|
2036
2031
|
ConversationLocalesProvider,
|
|
2032
|
+
ConversationRow,
|
|
2037
2033
|
ConversationWallpaper,
|
|
2038
2034
|
ConversationsProvider,
|
|
2035
|
+
DEFAULT_ACCEPTED_FILE_TYPES,
|
|
2036
|
+
DEFAULT_AVATAR_LABELS,
|
|
2037
|
+
DEFAULT_CONVERSATION_CHANNEL,
|
|
2038
|
+
DEFAULT_CONVERSATION_CONTEXT_LABELS,
|
|
2039
|
+
DEFAULT_CONVERSATION_DOCUMENTS_LABELS,
|
|
2040
|
+
DEFAULT_CONVERSATION_HEADER_LABELS,
|
|
2041
|
+
DEFAULT_CONVERSATION_LIST_ITEM_LABELS,
|
|
2042
|
+
DEFAULT_DOCUMENTS_LIBRARY_LABELS,
|
|
2043
|
+
DEFAULT_EMOJI_PICKER_LABELS,
|
|
2044
|
+
DEFAULT_INTERACTIVE_MESSAGE_LABELS,
|
|
2045
|
+
DEFAULT_LIGHTBOX_LABELS,
|
|
2046
|
+
DEFAULT_MESSAGE_COMPOSER_LABELS,
|
|
2047
|
+
DEFAULT_TEMPLATES_SETTINGS_LABELS,
|
|
2048
|
+
DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS,
|
|
2049
|
+
DEFAULT_WINDOW_EXPIRED_LABELS,
|
|
2050
|
+
DOCUMENT_SOURCE_FILTER,
|
|
2039
2051
|
DateDivider,
|
|
2052
|
+
DocumentsLibrary,
|
|
2053
|
+
EMOJI_CATEGORIES,
|
|
2040
2054
|
EmojiPicker,
|
|
2041
2055
|
FileIcon,
|
|
2056
|
+
HANDLE_KIND,
|
|
2057
|
+
InteractiveMessage,
|
|
2042
2058
|
Lightbox,
|
|
2043
2059
|
MediaRenderer,
|
|
2044
2060
|
MessageBubble,
|
|
@@ -2046,21 +2062,41 @@ export {
|
|
|
2046
2062
|
MessageTail,
|
|
2047
2063
|
MessageText,
|
|
2048
2064
|
MessageTimestamp,
|
|
2065
|
+
NARROW_MAX_WIDTH_PX,
|
|
2066
|
+
REOPEN_MECHANISM,
|
|
2049
2067
|
SimpleEmojiPicker,
|
|
2050
2068
|
StatusTicks,
|
|
2069
|
+
TEMPLATE_SETTINGS_TAB,
|
|
2051
2070
|
ToastProvider,
|
|
2052
2071
|
TopicsForm,
|
|
2072
|
+
WINDOW_FILTERS,
|
|
2053
2073
|
WelcomeFarewellForm,
|
|
2054
2074
|
WhatsAppCreateTemplateForm,
|
|
2055
2075
|
WhatsAppMessageEditor,
|
|
2056
2076
|
WhatsAppTemplateSettingsForm,
|
|
2077
|
+
WhatsAppTemplatesSettings,
|
|
2078
|
+
WindowExpiredNotice,
|
|
2079
|
+
buildTranscriptFilename,
|
|
2080
|
+
buildTranscriptText,
|
|
2081
|
+
capabilitiesOf,
|
|
2082
|
+
channelFiltersFor,
|
|
2083
|
+
contactFlag,
|
|
2084
|
+
createMediaUrlResolver,
|
|
2085
|
+
downloadTextFile,
|
|
2086
|
+
formatContactHandle,
|
|
2087
|
+
formatDateTime,
|
|
2057
2088
|
formatFileSize,
|
|
2058
2089
|
formatPhone,
|
|
2090
|
+
formatStalledFor,
|
|
2059
2091
|
formatTimestamp,
|
|
2060
2092
|
htmlToWA,
|
|
2093
|
+
isSameDay,
|
|
2094
|
+
isWindowBlocking,
|
|
2061
2095
|
parseWhatsAppFormatting,
|
|
2062
2096
|
phoneInitials,
|
|
2097
|
+
searchEmojis,
|
|
2063
2098
|
toast,
|
|
2099
|
+
useConversationActions,
|
|
2064
2100
|
useConversationContext,
|
|
2065
2101
|
useConversationDocuments,
|
|
2066
2102
|
useConversationList,
|
|
@@ -2070,8 +2106,12 @@ export {
|
|
|
2070
2106
|
useConversations,
|
|
2071
2107
|
useDarkMode,
|
|
2072
2108
|
useGlobalRealtime,
|
|
2109
|
+
useInboxActions,
|
|
2110
|
+
useIsDarkTheme,
|
|
2111
|
+
useIsNarrow,
|
|
2073
2112
|
useToast,
|
|
2074
2113
|
useWaitingNotifications,
|
|
2075
2114
|
waToHTML,
|
|
2076
|
-
waToHTMLInline
|
|
2115
|
+
waToHTMLInline,
|
|
2116
|
+
windowOf
|
|
2077
2117
|
};
|