@adatechnology/conversations-ui 0.1.0-rc.2 → 0.1.0-rc.21
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-TV4OQRGH.js +2187 -0
- package/dist/flows/index.d.ts +14 -2
- package/dist/flows/index.js +117 -39
- package/dist/index.d.ts +862 -136
- package/dist/index.js +1746 -1124
- package/dist/preview/index.d.ts +470 -0
- package/dist/preview/index.js +1329 -0
- package/dist/styles.css +228 -0
- package/dist/types-C6A_9edv.d.ts +456 -0
- package/package.json +10 -3
- package/src/AudioRecorderButton.test.tsx +30 -0
- package/src/AudioRecorderButton.tsx +248 -0
- package/src/AudioTranscription.test.tsx +115 -0
- package/src/AudioTranscription.tsx +249 -0
- package/src/Avatar.tsx +30 -4
- package/src/ChannelIcon.tsx +87 -0
- package/src/ConversationContextPanel.tsx +280 -0
- package/src/ConversationDocumentsPanel.tsx +425 -0
- package/src/ConversationHeader.tsx +257 -0
- package/src/ConversationListItem.tsx +54 -7
- package/src/ConversationLocalesProvider.tsx +44 -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 +96 -22
- package/src/MessageBubble.tsx +77 -5
- package/src/MessageComposer.test.tsx +35 -0
- package/src/MessageComposer.tsx +165 -19
- package/src/RichMessageComposer.test.tsx +83 -0
- package/src/RichMessageComposer.tsx +380 -0
- package/src/Wallpaper.test.tsx +21 -0
- package/src/Wallpaper.tsx +69 -7
- package/src/WhatsAppMessageEditor.tsx +28 -4
- package/src/WindowExpiredNotice.tsx +57 -0
- package/src/audioRecorderFormat.test.ts +67 -0
- package/src/conversationChannel.test.ts +53 -0
- package/src/conversationChannel.ts +146 -0
- package/src/conversationTranscript.test.ts +122 -0
- package/src/conversationTranscript.ts +89 -0
- package/src/conversationWindow.test.ts +90 -0
- package/src/conversationWindow.ts +78 -0
- package/src/documentTypeLabel.test.ts +57 -0
- package/src/emojiCatalog.test.ts +35 -0
- package/src/emojiCatalog.ts +189 -0
- package/src/flows/FlowGroupHeader.tsx +12 -2
- package/src/flows/FlowMapCanvas.tsx +17 -14
- package/src/flows/FlowMapNode.tsx +3 -1
- package/src/flows/FlowNodeCard.tsx +22 -4
- package/src/flows/FlowNodePanel.tsx +132 -35
- package/src/flows/FlowPalette.tsx +6 -2
- package/src/flows/FlowWhatsAppPreview.tsx +14 -3
- package/src/flows/flowGraph.ts +5 -5
- package/src/flows/labels.ts +5 -0
- 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/hooks/useScrollToLatestMessage.ts +127 -0
- package/src/index.ts +140 -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/ConversationPreview.tsx +334 -0
- package/src/preview/MediaTypesPreview.tsx +87 -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/createPreviewBridgeClient.test.ts +92 -0
- package/src/preview/createPreviewBridgeClient.ts +124 -0
- package/src/preview/createPreviewMediaUploader.ts +82 -0
- package/src/preview/createPreviewWebhookClient.test.ts +194 -0
- package/src/preview/createPreviewWebhookClient.ts +222 -0
- package/src/preview/index.ts +67 -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/previewMediaUploader.test.ts +61 -0
- package/src/preview/previewStore.ts +193 -0
- package/src/preview/startPreviewScript.ts +60 -0
- package/src/providers/types.ts +175 -12
- package/src/quickReply.test.ts +58 -0
- package/src/settings/TranscriptionSettingsForm.test.tsx +81 -0
- package/src/settings/TranscriptionSettingsForm.tsx +189 -0
- package/src/settings/WhatsAppCreateTemplateForm.tsx +3 -1
- package/src/settings/WhatsAppTemplatesSettings.tsx +106 -0
- package/src/styles.css +173 -0
- package/src/types.ts +72 -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,759 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AudioPlayer,
|
|
3
|
+
AudioRecorderButton,
|
|
4
|
+
AudioTranscription,
|
|
5
|
+
ConversationDocumentsPanel,
|
|
6
|
+
ConversationLocalesProvider,
|
|
7
|
+
ConversationWallpaper,
|
|
8
|
+
ConversationsProvider,
|
|
9
|
+
DEFAULT_ACCEPTED_FILE_TYPES,
|
|
10
|
+
DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
|
|
11
|
+
DEFAULT_CONVERSATION_DOCUMENTS_LABELS,
|
|
12
|
+
DEFAULT_DOCUMENTS_LIBRARY_LABELS,
|
|
13
|
+
DEFAULT_EMOJI_PICKER_LABELS,
|
|
14
|
+
DEFAULT_INTERACTIVE_MESSAGE_LABELS,
|
|
15
|
+
DEFAULT_LIGHTBOX_LABELS,
|
|
16
|
+
DEFAULT_MAX_RECORDING_MILLISECONDS,
|
|
17
|
+
DEFAULT_MESSAGE_COMPOSER_LABELS,
|
|
18
|
+
DOCUMENT_SOURCE_FILTER,
|
|
19
|
+
DateDivider,
|
|
20
|
+
DocumentsLibrary,
|
|
21
|
+
EMOJI_CATEGORIES,
|
|
22
|
+
EmojiPicker,
|
|
23
|
+
FileIcon,
|
|
24
|
+
InteractiveMessage,
|
|
25
|
+
Lightbox,
|
|
26
|
+
MediaRenderer,
|
|
27
|
+
MessageBubble,
|
|
28
|
+
MessageComposer,
|
|
29
|
+
StatusTicks,
|
|
30
|
+
applyQuickReplyVariables,
|
|
31
|
+
cn,
|
|
32
|
+
conversationsOf,
|
|
33
|
+
createMediaUrlResolver,
|
|
34
|
+
formatDateTime,
|
|
35
|
+
formatFileSize,
|
|
36
|
+
formatPhone,
|
|
37
|
+
formatTimestamp,
|
|
38
|
+
isSameDay,
|
|
39
|
+
phoneCountryFlag,
|
|
40
|
+
phoneInitials,
|
|
41
|
+
resolveQuickReply,
|
|
42
|
+
searchEmojis,
|
|
43
|
+
totalOf,
|
|
44
|
+
useAsyncResource,
|
|
45
|
+
useConversationDocuments,
|
|
46
|
+
useConversationLocales,
|
|
47
|
+
useConversations
|
|
48
|
+
} from "./chunk-TV4OQRGH.js";
|
|
1
49
|
import {
|
|
2
50
|
htmlToWA,
|
|
3
51
|
parseWhatsAppFormatting,
|
|
4
52
|
useDarkMode,
|
|
53
|
+
useIsDarkTheme,
|
|
5
54
|
waToHTML,
|
|
6
55
|
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
|
-
}
|
|
56
|
+
} from "./chunk-2AYDBWNE.js";
|
|
414
57
|
|
|
415
|
-
// src/
|
|
416
|
-
import {
|
|
417
|
-
|
|
418
|
-
return /* @__PURE__ */ jsx8("div", { className: `cv-wallpaper ${className}`.trim(), children });
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// src/EmojiPicker.tsx
|
|
422
|
-
import { useState as useState4, useCallback } from "react";
|
|
423
|
-
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
424
|
-
var EMOJI_CATEGORIES = [
|
|
425
|
-
{
|
|
426
|
-
name: "Smileys",
|
|
427
|
-
emojis: ["\u{1F600}", "\u{1F603}", "\u{1F604}", "\u{1F601}", "\u{1F605}", "\u{1F602}", "\u{1F923}", "\u{1F60A}", "\u{1F607}", "\u{1F642}", "\u{1F609}", "\u{1F60C}", "\u{1F60D}", "\u{1F970}", "\u{1F618}", "\u{1F617}", "\u{1F60B}", "\u{1F61B}", "\u{1F61C}", "\u{1F92A}"]
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
name: "Gestures",
|
|
431
|
-
emojis: ["\u{1F44D}", "\u{1F44E}", "\u{1F44C}", "\u270C\uFE0F", "\u{1F91E}", "\u{1F91F}", "\u{1F918}", "\u{1F919}", "\u{1F44B}", "\u{1F91A}", "\u{1F590}\uFE0F", "\u270B", "\u{1F596}", "\u{1F44F}", "\u{1F64C}", "\u{1F91D}", "\u{1F64F}", "\u270D\uFE0F", "\u{1F485}", "\u{1F933}"]
|
|
432
|
-
},
|
|
433
|
-
{
|
|
434
|
-
name: "Hearts",
|
|
435
|
-
emojis: ["\u2764\uFE0F", "\u{1F9E1}", "\u{1F49B}", "\u{1F49A}", "\u{1F499}", "\u{1F49C}", "\u{1F5A4}", "\u{1F90D}", "\u{1F90E}", "\u{1F494}", "\u2763\uFE0F", "\u{1F495}", "\u{1F49E}", "\u{1F493}", "\u{1F497}", "\u{1F496}", "\u{1F498}", "\u{1F49D}", "\u{1F49F}", "\u2665\uFE0F"]
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
name: "Food",
|
|
439
|
-
emojis: ["\u{1F354}", "\u{1F35F}", "\u{1F355}", "\u{1F32D}", "\u{1F37F}", "\u{1F9C2}", "\u{1F953}", "\u{1F95A}", "\u{1F373}", "\u{1F9C7}", "\u{1F95E}", "\u{1F9C8}", "\u{1F35E}", "\u{1F950}", "\u{1F968}", "\u{1F96F}", "\u{1F956}", "\u{1F9C0}", "\u{1F957}", "\u{1F959}"]
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
name: "Drinks",
|
|
443
|
-
emojis: ["\u2615", "\u{1F375}", "\u{1F376}", "\u{1F37E}", "\u{1F377}", "\u{1F378}", "\u{1F379}", "\u{1F37A}", "\u{1F37B}", "\u{1F942}", "\u{1F943}", "\u{1F964}", "\u{1F9CB}", "\u{1F9C3}", "\u{1F9C9}", "\u{1F9CA}", "\u{1F962}", "\u{1F37D}\uFE0F", "\u{1F374}", "\u{1F944}"]
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
name: "Objects",
|
|
447
|
-
emojis: ["\u{1F381}", "\u{1F382}", "\u{1F388}", "\u{1F389}", "\u{1F38A}", "\u{1F380}", "\u{1F4F1}", "\u{1F4BB}", "\u231A", "\u{1F4F7}", "\u{1F511}", "\u{1F4B0}", "\u{1F4B3}", "\u{1F4DD}", "\u{1F4CC}", "\u{1F4CD}", "\u2702\uFE0F", "\u{1F50D}", "\u{1F4A1}", "\u{1F514}"]
|
|
448
|
-
}
|
|
449
|
-
];
|
|
450
|
-
var EmojiPicker = ({ onSelect, className = "" }) => {
|
|
451
|
-
const [activeCategory, setActiveCategory] = useState4(0);
|
|
452
|
-
const handleSelect = useCallback(
|
|
453
|
-
(emoji) => {
|
|
454
|
-
onSelect(emoji);
|
|
455
|
-
},
|
|
456
|
-
[onSelect]
|
|
457
|
-
);
|
|
458
|
-
return /* @__PURE__ */ jsxs6("div", { className: `bg-white border border-gray-200 rounded-lg shadow-lg overflow-hidden ${className}`, children: [
|
|
459
|
-
/* @__PURE__ */ jsx9("div", { className: "flex border-b border-gray-200 overflow-x-auto", children: EMOJI_CATEGORIES.map((category, index) => /* @__PURE__ */ jsxs6(
|
|
460
|
-
"button",
|
|
461
|
-
{
|
|
462
|
-
onClick: () => setActiveCategory(index),
|
|
463
|
-
className: `px-3 py-2 text-xs font-medium whitespace-nowrap border-b-2 transition-colors ${activeCategory === index ? "border-blue-500 text-blue-600" : "border-transparent text-gray-500 hover:text-gray-700"}`,
|
|
464
|
-
children: [
|
|
465
|
-
category.emojis[0],
|
|
466
|
-
" ",
|
|
467
|
-
category.name
|
|
468
|
-
]
|
|
469
|
-
},
|
|
470
|
-
category.name
|
|
471
|
-
)) }),
|
|
472
|
-
/* @__PURE__ */ jsx9("div", { className: "grid grid-cols-10 gap-0.5 p-2 max-h-[240px] overflow-y-auto", children: EMOJI_CATEGORIES[activeCategory].emojis.map((emoji) => /* @__PURE__ */ jsx9(
|
|
473
|
-
"button",
|
|
474
|
-
{
|
|
475
|
-
onClick: () => handleSelect(emoji),
|
|
476
|
-
className: "w-8 h-8 flex items-center justify-center text-lg hover:bg-gray-100 rounded transition-colors cursor-pointer",
|
|
477
|
-
"aria-label": emoji,
|
|
478
|
-
children: emoji
|
|
479
|
-
},
|
|
480
|
-
emoji
|
|
481
|
-
)) })
|
|
482
|
-
] });
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
// src/MessageComposer.tsx
|
|
486
|
-
import { useState as useState5, useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
487
|
-
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
488
|
-
var DEFAULT_ACCEPTED_FILE_TYPES = "image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx,.zip";
|
|
489
|
-
var MessageComposer = ({
|
|
490
|
-
onSend,
|
|
491
|
-
onAttach,
|
|
492
|
-
value: externalValue,
|
|
493
|
-
onChange: externalOnChange,
|
|
494
|
-
features,
|
|
495
|
-
placeholder = "Digite uma mensagem...",
|
|
496
|
-
maxLength,
|
|
497
|
-
disabled = false,
|
|
498
|
-
acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES
|
|
499
|
-
}) => {
|
|
500
|
-
const [internalText, setInternalText] = useState5("");
|
|
501
|
-
const [showEmoji, setShowEmoji] = useState5(false);
|
|
502
|
-
const [attachments, setAttachments] = useState5([]);
|
|
503
|
-
const textareaRef = useRef2(null);
|
|
504
|
-
const fileInputRef = useRef2(null);
|
|
505
|
-
const isControlled = externalValue !== void 0;
|
|
506
|
-
const text = isControlled ? externalValue : internalText;
|
|
507
|
-
const setText = useCallback2((newText) => {
|
|
508
|
-
if (isControlled) {
|
|
509
|
-
externalOnChange?.(newText);
|
|
510
|
-
} else {
|
|
511
|
-
setInternalText(newText);
|
|
512
|
-
}
|
|
513
|
-
}, [isControlled, externalOnChange]);
|
|
514
|
-
const showEmojiButton = features?.emoji !== false;
|
|
515
|
-
const showAttachButton = features?.documents !== false;
|
|
516
|
-
const sendMessage = useCallback2(() => {
|
|
517
|
-
const trimmed = text.trim();
|
|
518
|
-
if (!trimmed && attachments.length === 0) return;
|
|
519
|
-
if (trimmed) onSend(trimmed);
|
|
520
|
-
for (const a of attachments) {
|
|
521
|
-
onAttach?.(a.file);
|
|
522
|
-
URL.revokeObjectURL(a.previewUrl);
|
|
523
|
-
}
|
|
524
|
-
if (!isControlled) setInternalText("");
|
|
525
|
-
setAttachments([]);
|
|
526
|
-
setShowEmoji(false);
|
|
527
|
-
if (textareaRef.current) textareaRef.current.style.height = "auto";
|
|
528
|
-
}, [text, attachments, onSend, onAttach, isControlled]);
|
|
529
|
-
const handleKeyDown = useCallback2((e) => {
|
|
530
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
531
|
-
e.preventDefault();
|
|
532
|
-
if (!disabled) sendMessage();
|
|
533
|
-
}
|
|
534
|
-
}, [sendMessage, disabled]);
|
|
535
|
-
const handleInput = useCallback2(() => {
|
|
536
|
-
const ta = textareaRef.current;
|
|
537
|
-
if (!ta) return;
|
|
538
|
-
ta.style.height = "auto";
|
|
539
|
-
ta.style.height = `${Math.min(ta.scrollHeight, 100)}px`;
|
|
540
|
-
}, []);
|
|
541
|
-
const handleEmojiSelect = useCallback2((emoji) => {
|
|
542
|
-
const ta = textareaRef.current;
|
|
543
|
-
if (!ta) {
|
|
544
|
-
setText(text + emoji);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
const start = ta.selectionStart;
|
|
548
|
-
const end = ta.selectionEnd;
|
|
549
|
-
const newText = text.slice(0, start) + emoji + text.slice(end);
|
|
550
|
-
setText(newText);
|
|
551
|
-
requestAnimationFrame(() => {
|
|
552
|
-
ta.focus();
|
|
553
|
-
ta.setSelectionRange(start + emoji.length, start + emoji.length);
|
|
554
|
-
handleInput();
|
|
555
|
-
});
|
|
556
|
-
}, [text, setText, handleInput]);
|
|
557
|
-
const handleFileChange = useCallback2((e) => {
|
|
558
|
-
const files = e.target.files;
|
|
559
|
-
if (!files) return;
|
|
560
|
-
const previews = [];
|
|
561
|
-
for (let i = 0; i < files.length; i++) {
|
|
562
|
-
const file = files[i];
|
|
563
|
-
previews.push({ file, previewUrl: file.type.startsWith("image/") ? URL.createObjectURL(file) : "" });
|
|
564
|
-
}
|
|
565
|
-
setAttachments((prev) => [...prev, ...previews]);
|
|
566
|
-
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
567
|
-
}, []);
|
|
568
|
-
const removeAttachment = useCallback2((index) => {
|
|
569
|
-
setAttachments((prev) => {
|
|
570
|
-
const next = [...prev];
|
|
571
|
-
if (next[index].previewUrl) URL.revokeObjectURL(next[index].previewUrl);
|
|
572
|
-
next.splice(index, 1);
|
|
573
|
-
return next;
|
|
574
|
-
});
|
|
575
|
-
}, []);
|
|
576
|
-
const insertFormatting = useCallback2((marker) => {
|
|
577
|
-
const ta = textareaRef.current;
|
|
578
|
-
if (!ta) return;
|
|
579
|
-
const start = ta.selectionStart;
|
|
580
|
-
const end = ta.selectionEnd;
|
|
581
|
-
const sel = text.slice(start, end);
|
|
582
|
-
if (sel) {
|
|
583
|
-
setText(text.slice(0, start) + marker + sel + marker + text.slice(end));
|
|
584
|
-
requestAnimationFrame(() => {
|
|
585
|
-
ta.focus();
|
|
586
|
-
ta.setSelectionRange(start + marker.length + sel.length + marker.length, start + marker.length + sel.length + marker.length);
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
}, [text, setText]);
|
|
590
|
-
const canSend = text.trim().length > 0 || attachments.length > 0;
|
|
591
|
-
const remaining = maxLength ? maxLength - text.length : null;
|
|
592
|
-
return /* @__PURE__ */ jsxs7("div", { children: [
|
|
593
|
-
attachments.length > 0 && /* @__PURE__ */ jsx10("div", { className: "flex gap-2 px-1 pb-2 overflow-x-auto", children: attachments.map((a, i) => /* @__PURE__ */ jsxs7("div", { className: "relative flex-shrink-0", children: [
|
|
594
|
-
a.previewUrl ? /* @__PURE__ */ jsx10("img", { src: a.previewUrl, alt: "", className: "w-16 h-16 object-cover rounded-lg border border-gray-200" }) : /* @__PURE__ */ jsx10("div", { className: "w-16 h-16 bg-gray-100 rounded-lg border border-gray-200 flex items-center justify-center", children: /* @__PURE__ */ jsxs7("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#9ca3af", strokeWidth: "1.5", children: [
|
|
595
|
-
/* @__PURE__ */ jsx10("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
596
|
-
/* @__PURE__ */ jsx10("polyline", { points: "14 2 14 8 20 8" })
|
|
597
|
-
] }) }),
|
|
598
|
-
/* @__PURE__ */ jsx10("button", { onClick: () => removeAttachment(i), className: "absolute -top-2 -right-2 w-5 h-5 bg-gray-600 text-white rounded-full flex items-center justify-center hover:bg-gray-800 text-xs", children: "\u2715" })
|
|
599
|
-
] }, i)) }),
|
|
600
|
-
/* @__PURE__ */ jsxs7("div", { className: "flex items-end gap-1.5 bg-[#f0f2f5] rounded-xl px-3 py-2", children: [
|
|
601
|
-
showEmojiButton && /* @__PURE__ */ jsxs7("div", { className: "relative flex-shrink-0", children: [
|
|
602
|
-
/* @__PURE__ */ jsx10("button", { onClick: () => setShowEmoji((v) => !v), className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 transition-colors", "aria-label": "Emoji", children: /* @__PURE__ */ jsxs7("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
603
|
-
/* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "10" }),
|
|
604
|
-
/* @__PURE__ */ jsx10("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
605
|
-
/* @__PURE__ */ jsx10("circle", { cx: "9", cy: "9", r: "0.5", fill: "currentColor" }),
|
|
606
|
-
/* @__PURE__ */ jsx10("circle", { cx: "15", cy: "9", r: "0.5", fill: "currentColor" })
|
|
607
|
-
] }) }),
|
|
608
|
-
showEmoji && /* @__PURE__ */ jsx10("div", { className: "absolute bottom-full left-0 mb-2 z-10", children: /* @__PURE__ */ jsx10(EmojiPicker, { onSelect: handleEmojiSelect }) })
|
|
609
|
-
] }),
|
|
610
|
-
/* @__PURE__ */ jsx10(
|
|
611
|
-
"textarea",
|
|
612
|
-
{
|
|
613
|
-
ref: textareaRef,
|
|
614
|
-
value: text,
|
|
615
|
-
onChange: (e) => {
|
|
616
|
-
setText(e.target.value);
|
|
617
|
-
handleInput();
|
|
618
|
-
},
|
|
619
|
-
onKeyDown: handleKeyDown,
|
|
620
|
-
placeholder,
|
|
621
|
-
rows: 1,
|
|
622
|
-
disabled,
|
|
623
|
-
className: "flex-1 resize-none bg-transparent text-[15px] text-[#3b4a54] placeholder-[#8696a0] outline-none py-1.5 max-h-[100px] leading-relaxed",
|
|
624
|
-
style: { fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" }
|
|
625
|
-
}
|
|
626
|
-
),
|
|
627
|
-
showAttachButton && /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
628
|
-
/* @__PURE__ */ jsx10("input", { ref: fileInputRef, type: "file", multiple: true, accept: acceptedFileTypes, onChange: handleFileChange, className: "hidden" }),
|
|
629
|
-
/* @__PURE__ */ jsx10("button", { onClick: () => fileInputRef.current?.click(), className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 flex-shrink-0 transition-colors", "aria-label": "Anexar", children: /* @__PURE__ */ jsx10("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx10("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }) })
|
|
630
|
-
] }),
|
|
631
|
-
/* @__PURE__ */ jsx10(
|
|
632
|
-
"button",
|
|
633
|
-
{
|
|
634
|
-
onClick: sendMessage,
|
|
635
|
-
disabled: !canSend || disabled,
|
|
636
|
-
className: `w-10 h-10 flex items-center justify-center rounded-full flex-shrink-0 transition-all ${canSend && !disabled ? "bg-[#00a884] text-white hover:bg-[#06cf9c] shadow-sm" : "bg-gray-200 text-gray-400 cursor-not-allowed"}`,
|
|
637
|
-
"aria-label": "Enviar",
|
|
638
|
-
children: /* @__PURE__ */ jsx10("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx10("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
|
|
639
|
-
}
|
|
640
|
-
)
|
|
641
|
-
] }),
|
|
642
|
-
remaining !== null && /* @__PURE__ */ jsx10("div", { className: "flex justify-end mt-1 pr-1", children: /* @__PURE__ */ jsx10("span", { className: `text-xs ${remaining < 20 ? "text-red-500" : "text-gray-400"}`, children: remaining }) })
|
|
643
|
-
] });
|
|
644
|
-
};
|
|
645
|
-
|
|
646
|
-
// src/WhatsAppMessageEditor.tsx
|
|
647
|
-
import { useRef as useRef3 } from "react";
|
|
648
|
-
import { Bold, Italic, Strikethrough } from "lucide-react";
|
|
649
|
-
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
650
|
-
var INLINE_TOKEN = /(\*[^*\n]+\*|_[^_\n]+_|~[^~\n]+~|`[^`\n]+`|\{[a-zA-Z_]+\})/g;
|
|
651
|
-
function renderLine(line, lineKey) {
|
|
652
|
-
const nodes = [];
|
|
653
|
-
let lastIndex = 0;
|
|
654
|
-
let match;
|
|
655
|
-
let tokenIndex = 0;
|
|
656
|
-
INLINE_TOKEN.lastIndex = 0;
|
|
657
|
-
while ((match = INLINE_TOKEN.exec(line)) !== null) {
|
|
658
|
-
if (match.index > lastIndex) nodes.push(line.slice(lastIndex, match.index));
|
|
659
|
-
const token = match[0];
|
|
660
|
-
const key = `${lineKey}-${tokenIndex++}`;
|
|
661
|
-
if (token.startsWith("*")) nodes.push(/* @__PURE__ */ jsx11("strong", { children: token.slice(1, -1) }, key));
|
|
662
|
-
else if (token.startsWith("_")) nodes.push(/* @__PURE__ */ jsx11("em", { children: token.slice(1, -1) }, key));
|
|
663
|
-
else if (token.startsWith("~")) nodes.push(/* @__PURE__ */ jsx11("s", { children: token.slice(1, -1) }, key));
|
|
664
|
-
else if (token.startsWith("`")) nodes.push(/* @__PURE__ */ jsx11("code", { className: "font-mono text-[0.85em]", children: token.slice(1, -1) }, key));
|
|
665
|
-
else nodes.push(/* @__PURE__ */ jsx11("span", { className: "rounded bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 px-1", children: token }, key));
|
|
666
|
-
lastIndex = INLINE_TOKEN.lastIndex;
|
|
667
|
-
}
|
|
668
|
-
if (lastIndex < line.length) nodes.push(line.slice(lastIndex));
|
|
669
|
-
return nodes;
|
|
670
|
-
}
|
|
671
|
-
function renderWhatsApp(text) {
|
|
672
|
-
const lines = text.split("\n");
|
|
673
|
-
return lines.map((line, index) => /* @__PURE__ */ jsxs8("span", { children: [
|
|
674
|
-
renderLine(line, `ln-${index}`),
|
|
675
|
-
index < lines.length - 1 ? /* @__PURE__ */ jsx11("br", {}) : null
|
|
676
|
-
] }, `ln-${index}`));
|
|
677
|
-
}
|
|
678
|
-
function WhatsAppMessageEditor({
|
|
679
|
-
value,
|
|
680
|
-
onChange,
|
|
681
|
-
placeholder,
|
|
682
|
-
placeholders = [],
|
|
683
|
-
rows = 4,
|
|
684
|
-
previewLabel = "Pr\xE9via (como aparece no WhatsApp)",
|
|
685
|
-
emptyPreviewText = "Sua mensagem aparecer\xE1 aqui\u2026"
|
|
686
|
-
}) {
|
|
687
|
-
const textareaRef = useRef3(null);
|
|
688
|
-
function wrapSelection(marker) {
|
|
689
|
-
const textarea = textareaRef.current;
|
|
690
|
-
if (!textarea) return;
|
|
691
|
-
const start = textarea.selectionStart;
|
|
692
|
-
const end = textarea.selectionEnd;
|
|
693
|
-
const selected = value.slice(start, end) || "texto";
|
|
694
|
-
const next = value.slice(0, start) + marker + selected + marker + value.slice(end);
|
|
695
|
-
onChange(next);
|
|
696
|
-
requestAnimationFrame(() => {
|
|
697
|
-
textarea.focus();
|
|
698
|
-
textarea.setSelectionRange(start + marker.length, start + marker.length + selected.length);
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
|
-
function insertAtCursor(snippet) {
|
|
702
|
-
const textarea = textareaRef.current;
|
|
703
|
-
if (!textarea) return;
|
|
704
|
-
const start = textarea.selectionStart;
|
|
705
|
-
const end = textarea.selectionEnd;
|
|
706
|
-
const next = value.slice(0, start) + snippet + value.slice(end);
|
|
707
|
-
onChange(next);
|
|
708
|
-
requestAnimationFrame(() => {
|
|
709
|
-
textarea.focus();
|
|
710
|
-
const caret = start + snippet.length;
|
|
711
|
-
textarea.setSelectionRange(caret, caret);
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
const toolbarButtonClass = "inline-flex items-center justify-center h-8 w-8 rounded-lg border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors";
|
|
715
|
-
return /* @__PURE__ */ jsxs8("div", { children: [
|
|
716
|
-
/* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-center gap-1.5 mb-2", children: [
|
|
717
|
-
/* @__PURE__ */ jsx11("button", { type: "button", onClick: () => wrapSelection("*"), className: toolbarButtonClass, title: "Negrito (*texto*)", "aria-label": "Negrito", children: /* @__PURE__ */ jsx11(Bold, { size: 15 }) }),
|
|
718
|
-
/* @__PURE__ */ jsx11("button", { type: "button", onClick: () => wrapSelection("_"), className: toolbarButtonClass, title: "It\xE1lico (_texto_)", "aria-label": "It\xE1lico", children: /* @__PURE__ */ jsx11(Italic, { size: 15 }) }),
|
|
719
|
-
/* @__PURE__ */ jsx11("button", { type: "button", onClick: () => wrapSelection("~"), className: toolbarButtonClass, title: "Tachado (~texto~)", "aria-label": "Tachado", children: /* @__PURE__ */ jsx11(Strikethrough, { size: 15 }) }),
|
|
720
|
-
placeholders.length > 0 && /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
721
|
-
/* @__PURE__ */ jsx11("span", { className: "mx-1 h-5 w-px bg-gray-200 dark:bg-gray-600" }),
|
|
722
|
-
placeholders.map((token) => /* @__PURE__ */ jsx11(
|
|
723
|
-
"button",
|
|
724
|
-
{
|
|
725
|
-
type: "button",
|
|
726
|
-
onClick: () => insertAtCursor(token),
|
|
727
|
-
className: "inline-flex items-center h-8 px-2 rounded-lg border border-gray-200 dark:border-gray-600 text-xs text-blue-600 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/40 transition-colors",
|
|
728
|
-
title: `Inserir ${token}`,
|
|
729
|
-
children: token
|
|
730
|
-
},
|
|
731
|
-
token
|
|
732
|
-
))
|
|
733
|
-
] })
|
|
734
|
-
] }),
|
|
735
|
-
/* @__PURE__ */ jsx11(
|
|
736
|
-
"textarea",
|
|
737
|
-
{
|
|
738
|
-
ref: textareaRef,
|
|
739
|
-
value,
|
|
740
|
-
onChange: (event) => onChange(event.target.value),
|
|
741
|
-
placeholder,
|
|
742
|
-
rows,
|
|
743
|
-
className: "w-full border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition-all placeholder:text-gray-400 dark:placeholder:text-gray-500 resize-y"
|
|
744
|
-
}
|
|
745
|
-
),
|
|
746
|
-
/* @__PURE__ */ jsxs8("div", { className: "mt-2", children: [
|
|
747
|
-
/* @__PURE__ */ jsx11("p", { className: "text-xs text-gray-500 dark:text-gray-400 mb-1", children: previewLabel }),
|
|
748
|
-
/* @__PURE__ */ jsx11("div", { className: "rounded-xl bg-[#e5ded8] dark:bg-gray-900 p-3", children: /* @__PURE__ */ jsx11("div", { className: "inline-block max-w-full rounded-lg rounded-tl-none bg-white dark:bg-gray-800 shadow-sm px-3 py-2 text-sm text-gray-800 dark:text-gray-100 whitespace-pre-wrap break-words", children: value.trim() ? renderWhatsApp(value) : /* @__PURE__ */ jsx11("span", { className: "text-gray-400 dark:text-gray-500", children: emptyPreviewText }) }) })
|
|
749
|
-
] })
|
|
750
|
-
] });
|
|
751
|
-
}
|
|
58
|
+
// src/RichMessageComposer.tsx
|
|
59
|
+
import { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef as useRef2, useState as useState2 } from "react";
|
|
60
|
+
import { Bold, Italic, Strikethrough, Code, Paperclip, SendHorizonal, Braces } from "lucide-react";
|
|
752
61
|
|
|
753
62
|
// src/SimpleEmojiPicker.tsx
|
|
754
|
-
import { useEffect
|
|
63
|
+
import { useEffect, useRef, useState } from "react";
|
|
755
64
|
import { Smile } from "lucide-react";
|
|
756
|
-
import { jsx
|
|
65
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
757
66
|
var EMOJIS = [
|
|
758
67
|
"\u{1F600}",
|
|
759
68
|
"\u{1F602}",
|
|
@@ -837,9 +146,9 @@ var EMOJIS = [
|
|
|
837
146
|
"\u{1F6E1}\uFE0F"
|
|
838
147
|
];
|
|
839
148
|
function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px", pickerMaxHeight = "220px" }) {
|
|
840
|
-
const [open, setOpen] =
|
|
841
|
-
const containerRef =
|
|
842
|
-
|
|
149
|
+
const [open, setOpen] = useState(false);
|
|
150
|
+
const containerRef = useRef(null);
|
|
151
|
+
useEffect(() => {
|
|
843
152
|
if (!open) return;
|
|
844
153
|
const handleClickOutside = (e) => {
|
|
845
154
|
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
@@ -849,23 +158,23 @@ function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px",
|
|
|
849
158
|
document.addEventListener("mousedown", handleClickOutside);
|
|
850
159
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
851
160
|
}, [open]);
|
|
852
|
-
return /* @__PURE__ */
|
|
853
|
-
/* @__PURE__ */
|
|
161
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative flex-shrink-0", children: [
|
|
162
|
+
/* @__PURE__ */ jsx(
|
|
854
163
|
"button",
|
|
855
164
|
{
|
|
856
165
|
type: "button",
|
|
857
166
|
onClick: () => setOpen((v) => !v),
|
|
858
167
|
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
168
|
title: label,
|
|
860
|
-
children: /* @__PURE__ */
|
|
169
|
+
children: /* @__PURE__ */ jsx(Smile, { size: 15 })
|
|
861
170
|
}
|
|
862
171
|
),
|
|
863
|
-
open && /* @__PURE__ */
|
|
172
|
+
open && /* @__PURE__ */ jsx("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__ */ jsx(
|
|
864
173
|
"div",
|
|
865
174
|
{
|
|
866
175
|
className: "grid grid-cols-10 gap-0.5 overflow-y-auto",
|
|
867
176
|
style: { width: pickerWidth, maxHeight: pickerMaxHeight },
|
|
868
|
-
children: EMOJIS.map((emoji) => /* @__PURE__ */
|
|
177
|
+
children: EMOJIS.map((emoji) => /* @__PURE__ */ jsx(
|
|
869
178
|
"button",
|
|
870
179
|
{
|
|
871
180
|
type: "button",
|
|
@@ -883,24 +192,378 @@ function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px",
|
|
|
883
192
|
] });
|
|
884
193
|
}
|
|
885
194
|
|
|
886
|
-
// src/
|
|
887
|
-
import { jsx as
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
195
|
+
// src/RichMessageComposer.tsx
|
|
196
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
197
|
+
var RICH_COMPOSER_ACTION = {
|
|
198
|
+
BOLD: "bold",
|
|
199
|
+
ITALIC: "italic",
|
|
200
|
+
STRIKETHROUGH: "strikethrough",
|
|
201
|
+
MONOSPACE: "monospace",
|
|
202
|
+
EMOJI: "emoji",
|
|
203
|
+
ATTACH: "attach",
|
|
204
|
+
VARIABLES: "variables"
|
|
205
|
+
};
|
|
206
|
+
var DEFAULT_RICH_COMPOSER_TOOLTIPS = {
|
|
207
|
+
bold: "Negrito",
|
|
208
|
+
italic: "It\xE1lico",
|
|
209
|
+
strikethrough: "Tachado",
|
|
210
|
+
monospace: "Monoespa\xE7ado",
|
|
211
|
+
emoji: "Emojis",
|
|
212
|
+
attach: "Anexar arquivo",
|
|
213
|
+
send: "Enviar",
|
|
214
|
+
variables: "Vari\xE1veis dispon\xEDveis"
|
|
215
|
+
};
|
|
216
|
+
var FORMATTING_ELEMENT = {
|
|
217
|
+
bold: "strong",
|
|
218
|
+
italic: "em",
|
|
219
|
+
strikethrough: "del",
|
|
220
|
+
monospace: "code"
|
|
221
|
+
};
|
|
222
|
+
var MONOSPACE_CLASS = "bg-black/5 dark:bg-white/10 rounded px-0.5 font-mono text-sm";
|
|
223
|
+
var RichMessageComposer = forwardRef(function RichMessageComposer2({
|
|
224
|
+
value,
|
|
225
|
+
onChange,
|
|
226
|
+
onSend,
|
|
227
|
+
onAttachFiles,
|
|
228
|
+
quickReplies,
|
|
229
|
+
variables,
|
|
230
|
+
toolbar,
|
|
231
|
+
tooltips,
|
|
232
|
+
placeholder,
|
|
233
|
+
disabled = false,
|
|
234
|
+
isSending = false,
|
|
235
|
+
idleAction,
|
|
236
|
+
attachmentsPreview,
|
|
237
|
+
acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES,
|
|
238
|
+
className
|
|
239
|
+
}, ref) {
|
|
240
|
+
const editorRef = useRef2(null);
|
|
241
|
+
const fileInputRef = useRef2(null);
|
|
242
|
+
const variablesRef = useRef2(null);
|
|
243
|
+
const [isVariablesOpen, setIsVariablesOpen] = useState2(false);
|
|
244
|
+
useEffect2(() => {
|
|
245
|
+
if (!isVariablesOpen) return;
|
|
246
|
+
const closeOnOutsideClick = (event) => {
|
|
247
|
+
if (!variablesRef.current?.contains(event.target)) setIsVariablesOpen(false);
|
|
248
|
+
};
|
|
249
|
+
document.addEventListener("mousedown", closeOnOutsideClick);
|
|
250
|
+
return () => document.removeEventListener("mousedown", closeOnOutsideClick);
|
|
251
|
+
}, [isVariablesOpen]);
|
|
252
|
+
const tooltipOf = (action) => tooltips?.[action] ?? DEFAULT_RICH_COMPOSER_TOOLTIPS[action];
|
|
253
|
+
const shows = (action) => toolbar?.[action] !== false;
|
|
254
|
+
const replaceContent = useCallback((text) => {
|
|
255
|
+
const editor = editorRef.current;
|
|
256
|
+
if (!editor) return;
|
|
257
|
+
editor.innerHTML = waToHTML(text);
|
|
258
|
+
onChange(htmlToWA(editor.innerHTML));
|
|
259
|
+
editor.focus();
|
|
260
|
+
}, [onChange]);
|
|
261
|
+
useImperativeHandle(ref, () => ({
|
|
262
|
+
setContent: replaceContent,
|
|
263
|
+
clear: () => {
|
|
264
|
+
const editor = editorRef.current;
|
|
265
|
+
if (!editor) return;
|
|
266
|
+
editor.innerHTML = "";
|
|
267
|
+
onChange("");
|
|
268
|
+
},
|
|
269
|
+
focus: () => editorRef.current?.focus()
|
|
270
|
+
}), [onChange, replaceContent]);
|
|
271
|
+
const currentRange = useCallback(() => {
|
|
272
|
+
const editor = editorRef.current;
|
|
273
|
+
if (!editor) return void 0;
|
|
274
|
+
editor.focus();
|
|
275
|
+
const selection = window.getSelection();
|
|
276
|
+
if (!selection?.rangeCount || !editor.contains(selection.anchorNode)) return void 0;
|
|
277
|
+
return selection.getRangeAt(0);
|
|
278
|
+
}, []);
|
|
279
|
+
const commitRange = useCallback((range, node) => {
|
|
280
|
+
const selection = window.getSelection();
|
|
281
|
+
range.setStartAfter(node);
|
|
282
|
+
range.collapse(true);
|
|
283
|
+
selection?.removeAllRanges();
|
|
284
|
+
selection?.addRange(range);
|
|
285
|
+
onChange(htmlToWA(editorRef.current?.innerHTML ?? ""));
|
|
286
|
+
}, [onChange]);
|
|
287
|
+
const insertAtCursor = useCallback((fragment) => {
|
|
288
|
+
const range = currentRange();
|
|
289
|
+
if (!range) return;
|
|
290
|
+
range.deleteContents();
|
|
291
|
+
const textNode = document.createTextNode(fragment);
|
|
292
|
+
range.insertNode(textNode);
|
|
293
|
+
commitRange(range, textNode);
|
|
294
|
+
}, [commitRange, currentRange]);
|
|
295
|
+
const wrapSelection = useCallback((action) => {
|
|
296
|
+
const range = currentRange();
|
|
297
|
+
const selectedText = range?.toString();
|
|
298
|
+
if (!range || !selectedText) return;
|
|
299
|
+
range.deleteContents();
|
|
300
|
+
const wrapper = document.createElement(FORMATTING_ELEMENT[action]);
|
|
301
|
+
if (action === "monospace") wrapper.className = MONOSPACE_CLASS;
|
|
302
|
+
wrapper.textContent = selectedText;
|
|
303
|
+
range.insertNode(wrapper);
|
|
304
|
+
commitRange(range, wrapper);
|
|
305
|
+
}, [commitRange, currentRange]);
|
|
306
|
+
const handleInput = useCallback((event) => {
|
|
307
|
+
onChange(htmlToWA(event.currentTarget.innerHTML));
|
|
308
|
+
}, [onChange]);
|
|
309
|
+
const handleKeyDown = useCallback((event) => {
|
|
310
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
311
|
+
event.preventDefault();
|
|
312
|
+
if (!disabled) onSend();
|
|
313
|
+
}
|
|
314
|
+
}, [disabled, onSend]);
|
|
315
|
+
const handleFileChange = useCallback((event) => {
|
|
316
|
+
if (event.target.files?.length) onAttachFiles?.(event.target.files);
|
|
317
|
+
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
318
|
+
}, [onAttachFiles]);
|
|
319
|
+
const canSend = value.trim().length > 0;
|
|
320
|
+
return /* @__PURE__ */ jsxs2("div", { className: cn("flex flex-col", className), children: [
|
|
321
|
+
attachmentsPreview,
|
|
322
|
+
quickReplies?.length ? (
|
|
323
|
+
/* Uma linha só, rolando na horizontal. Deixar quebrar em várias linhas faz a barra crescer
|
|
324
|
+
conforme o número de respostas rápidas e empurrar a conversa para cima — o composer
|
|
325
|
+
precisa ter altura previsível, independente de quantos atalhos o host configurou. */
|
|
326
|
+
/* @__PURE__ */ jsx2("div", { className: "mb-2 flex flex-nowrap gap-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: quickReplies.map((quickReply) => /* @__PURE__ */ jsx2(
|
|
327
|
+
"button",
|
|
328
|
+
{
|
|
329
|
+
type: "button",
|
|
330
|
+
title: quickReply.tooltip,
|
|
331
|
+
onClick: () => replaceContent(quickReply.text),
|
|
332
|
+
className: "flex-shrink-0 whitespace-nowrap rounded-full border border-gray-200 bg-white px-2 py-1 text-xs text-gray-600 transition-colors hover:border-teal-200 hover:bg-teal-50 hover:text-teal-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:border-teal-800 dark:hover:bg-teal-950/40 dark:hover:text-teal-400",
|
|
333
|
+
children: quickReply.label
|
|
334
|
+
},
|
|
335
|
+
quickReply.id
|
|
336
|
+
)) })
|
|
337
|
+
) : null,
|
|
338
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-end gap-2", children: [
|
|
339
|
+
/* @__PURE__ */ jsx2("div", { className: "mb-0.5 hidden items-center gap-0.5 sm:flex", children: [
|
|
340
|
+
[RICH_COMPOSER_ACTION.BOLD, Bold],
|
|
341
|
+
[RICH_COMPOSER_ACTION.ITALIC, Italic],
|
|
342
|
+
[RICH_COMPOSER_ACTION.STRIKETHROUGH, Strikethrough],
|
|
343
|
+
[RICH_COMPOSER_ACTION.MONOSPACE, Code]
|
|
344
|
+
].filter(([action]) => shows(action)).map(([action, Icon]) => /* @__PURE__ */ jsx2(
|
|
345
|
+
"button",
|
|
346
|
+
{
|
|
347
|
+
type: "button",
|
|
348
|
+
onClick: () => wrapSelection(action),
|
|
349
|
+
title: tooltipOf(action),
|
|
350
|
+
"aria-label": tooltipOf(action),
|
|
351
|
+
className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-teal-50 hover:text-teal-600 dark:text-gray-500 dark:hover:bg-teal-900/30 dark:hover:text-teal-400",
|
|
352
|
+
children: /* @__PURE__ */ jsx2(Icon, { size: 15 })
|
|
353
|
+
},
|
|
354
|
+
action
|
|
355
|
+
)) }),
|
|
356
|
+
shows(RICH_COMPOSER_ACTION.EMOJI) ? /* @__PURE__ */ jsx2(SimpleEmojiPicker, { onSelect: insertAtCursor, label: tooltipOf("emoji") }) : null,
|
|
357
|
+
variables?.length && shows(RICH_COMPOSER_ACTION.VARIABLES) ? /* @__PURE__ */ jsxs2("div", { ref: variablesRef, className: "relative", children: [
|
|
358
|
+
/* @__PURE__ */ jsx2(
|
|
359
|
+
"button",
|
|
360
|
+
{
|
|
361
|
+
type: "button",
|
|
362
|
+
onClick: () => setIsVariablesOpen((open) => !open),
|
|
363
|
+
title: tooltipOf("variables"),
|
|
364
|
+
"aria-label": tooltipOf("variables"),
|
|
365
|
+
"aria-expanded": isVariablesOpen,
|
|
366
|
+
className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full text-gray-400 transition-colors hover:bg-teal-50 hover:text-teal-600 dark:text-gray-500 dark:hover:bg-teal-900/30 dark:hover:text-teal-400",
|
|
367
|
+
children: /* @__PURE__ */ jsx2(Braces, { size: 18 })
|
|
368
|
+
}
|
|
369
|
+
),
|
|
370
|
+
isVariablesOpen ? /* @__PURE__ */ jsx2("div", { className: "absolute bottom-full left-0 z-20 mb-2 max-h-56 w-64 overflow-y-auto rounded-xl border border-gray-200 bg-white py-1 shadow-lg dark:border-gray-700 dark:bg-gray-800", children: variables.map((variable) => /* @__PURE__ */ jsxs2(
|
|
371
|
+
"button",
|
|
372
|
+
{
|
|
373
|
+
type: "button",
|
|
374
|
+
title: variable.tooltip ?? variable.value,
|
|
375
|
+
onClick: () => {
|
|
376
|
+
insertAtCursor(variable.value);
|
|
377
|
+
setIsVariablesOpen(false);
|
|
378
|
+
},
|
|
379
|
+
className: "flex w-full flex-col items-start px-3 py-1.5 text-left hover:bg-teal-50 dark:hover:bg-teal-950/40",
|
|
380
|
+
children: [
|
|
381
|
+
/* @__PURE__ */ jsx2("span", { className: "text-sm text-gray-800 dark:text-gray-100", children: variable.label }),
|
|
382
|
+
/* @__PURE__ */ jsx2("span", { className: "font-mono text-xs text-gray-400 dark:text-gray-500", children: variable.value })
|
|
383
|
+
]
|
|
384
|
+
},
|
|
385
|
+
variable.id
|
|
386
|
+
)) }) : null
|
|
387
|
+
] }) : null,
|
|
388
|
+
onAttachFiles && shows(RICH_COMPOSER_ACTION.ATTACH) ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
389
|
+
/* @__PURE__ */ jsx2(
|
|
390
|
+
"input",
|
|
391
|
+
{
|
|
392
|
+
ref: fileInputRef,
|
|
393
|
+
type: "file",
|
|
394
|
+
multiple: true,
|
|
395
|
+
accept: acceptedFileTypes,
|
|
396
|
+
onChange: handleFileChange,
|
|
397
|
+
className: "hidden"
|
|
398
|
+
}
|
|
399
|
+
),
|
|
400
|
+
/* @__PURE__ */ jsx2(
|
|
401
|
+
"button",
|
|
402
|
+
{
|
|
403
|
+
type: "button",
|
|
404
|
+
onClick: () => fileInputRef.current?.click(),
|
|
405
|
+
title: tooltipOf("attach"),
|
|
406
|
+
"aria-label": tooltipOf("attach"),
|
|
407
|
+
className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full text-gray-400 transition-colors hover:bg-teal-50 hover:text-teal-600 dark:text-gray-500 dark:hover:bg-teal-900/30 dark:hover:text-teal-400",
|
|
408
|
+
children: /* @__PURE__ */ jsx2(Paperclip, { size: 18 })
|
|
409
|
+
}
|
|
410
|
+
)
|
|
411
|
+
] }) : null,
|
|
412
|
+
/* @__PURE__ */ jsx2(
|
|
413
|
+
"div",
|
|
414
|
+
{
|
|
415
|
+
ref: editorRef,
|
|
416
|
+
contentEditable: !disabled,
|
|
417
|
+
suppressContentEditableWarning: true,
|
|
418
|
+
role: "textbox",
|
|
419
|
+
"aria-multiline": "true",
|
|
420
|
+
"aria-label": placeholder,
|
|
421
|
+
"data-placeholder": placeholder,
|
|
422
|
+
onInput: handleInput,
|
|
423
|
+
onKeyDown: handleKeyDown,
|
|
424
|
+
style: { minHeight: "36px", maxHeight: "120px" },
|
|
425
|
+
className: "flex-1 min-w-[180px] overflow-y-auto rounded-2xl border border-gray-200 bg-gray-50 px-4 py-2 text-sm text-gray-800 transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-teal-300 dark:border-gray-700 dark:bg-gray-700 dark:text-gray-100 empty:before:content-[attr(data-placeholder)] empty:before:text-gray-400 dark:empty:before:text-gray-500 before:pointer-events-none [&_strong]:font-semibold [&_em]:italic [&_del]:line-through [&_code]:rounded [&_code]:bg-black/5 [&_code]:px-0.5 [&_code]:font-mono [&_code]:text-sm dark:[&_code]:bg-white/10"
|
|
426
|
+
}
|
|
427
|
+
),
|
|
428
|
+
!canSend && idleAction ? /* @__PURE__ */ jsx2("div", { className: "flex-shrink-0", children: idleAction }) : /* @__PURE__ */ jsx2(
|
|
429
|
+
"button",
|
|
430
|
+
{
|
|
431
|
+
type: "button",
|
|
432
|
+
onClick: onSend,
|
|
433
|
+
disabled: disabled || isSending || !canSend,
|
|
434
|
+
title: tooltipOf("send"),
|
|
435
|
+
"aria-label": tooltipOf("send"),
|
|
436
|
+
className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-teal-600 text-white transition-colors hover:bg-teal-700 disabled:cursor-not-allowed disabled:opacity-40",
|
|
437
|
+
children: isSending ? /* @__PURE__ */ jsx2("span", { className: "text-xs", children: "\u23F3" }) : /* @__PURE__ */ jsx2(SendHorizonal, { size: 16 })
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
] })
|
|
441
|
+
] });
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// src/WhatsAppMessageEditor.tsx
|
|
445
|
+
import { useRef as useRef3 } from "react";
|
|
446
|
+
import { Bold as Bold2, Italic as Italic2, Strikethrough as Strikethrough2 } from "lucide-react";
|
|
447
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
448
|
+
var DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS = {
|
|
449
|
+
bold: "Negrito",
|
|
450
|
+
boldHint: "Negrito (*texto*)",
|
|
451
|
+
italic: "It\xE1lico",
|
|
452
|
+
italicHint: "It\xE1lico (_texto_)",
|
|
453
|
+
strikethrough: "Tachado",
|
|
454
|
+
strikethroughHint: "Tachado (~texto~)",
|
|
455
|
+
insertPlaceholder: (token) => `Inserir ${token}`
|
|
456
|
+
};
|
|
457
|
+
var INLINE_TOKEN = /(\*[^*\n]+\*|_[^_\n]+_|~[^~\n]+~|`[^`\n]+`|\{[a-zA-Z_]+\})/g;
|
|
458
|
+
function renderLine(line, lineKey) {
|
|
459
|
+
const nodes = [];
|
|
460
|
+
let lastIndex = 0;
|
|
461
|
+
let match;
|
|
462
|
+
let tokenIndex = 0;
|
|
463
|
+
INLINE_TOKEN.lastIndex = 0;
|
|
464
|
+
while ((match = INLINE_TOKEN.exec(line)) !== null) {
|
|
465
|
+
if (match.index > lastIndex) nodes.push(line.slice(lastIndex, match.index));
|
|
466
|
+
const token = match[0];
|
|
467
|
+
const key = `${lineKey}-${tokenIndex++}`;
|
|
468
|
+
if (token.startsWith("*")) nodes.push(/* @__PURE__ */ jsx3("strong", { children: token.slice(1, -1) }, key));
|
|
469
|
+
else if (token.startsWith("_")) nodes.push(/* @__PURE__ */ jsx3("em", { children: token.slice(1, -1) }, key));
|
|
470
|
+
else if (token.startsWith("~")) nodes.push(/* @__PURE__ */ jsx3("s", { children: token.slice(1, -1) }, key));
|
|
471
|
+
else if (token.startsWith("`")) nodes.push(/* @__PURE__ */ jsx3("code", { className: "font-mono text-[0.85em]", children: token.slice(1, -1) }, key));
|
|
472
|
+
else nodes.push(/* @__PURE__ */ jsx3("span", { className: "rounded bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 px-1", children: token }, key));
|
|
473
|
+
lastIndex = INLINE_TOKEN.lastIndex;
|
|
474
|
+
}
|
|
475
|
+
if (lastIndex < line.length) nodes.push(line.slice(lastIndex));
|
|
476
|
+
return nodes;
|
|
891
477
|
}
|
|
892
|
-
function
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
478
|
+
function renderWhatsApp(text) {
|
|
479
|
+
const lines = text.split("\n");
|
|
480
|
+
return lines.map((line, index) => /* @__PURE__ */ jsxs3("span", { children: [
|
|
481
|
+
renderLine(line, `ln-${index}`),
|
|
482
|
+
index < lines.length - 1 ? /* @__PURE__ */ jsx3("br", {}) : null
|
|
483
|
+
] }, `ln-${index}`));
|
|
484
|
+
}
|
|
485
|
+
function WhatsAppMessageEditor({
|
|
486
|
+
value,
|
|
487
|
+
onChange,
|
|
488
|
+
placeholder,
|
|
489
|
+
placeholders = [],
|
|
490
|
+
rows = 4,
|
|
491
|
+
previewLabel = "Pr\xE9via (como aparece no WhatsApp)",
|
|
492
|
+
emptyPreviewText = "Sua mensagem aparecer\xE1 aqui\u2026",
|
|
493
|
+
labels
|
|
494
|
+
}) {
|
|
495
|
+
const editorLabels = { ...DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS, ...labels };
|
|
496
|
+
const textareaRef = useRef3(null);
|
|
497
|
+
function wrapSelection(marker) {
|
|
498
|
+
const textarea = textareaRef.current;
|
|
499
|
+
if (!textarea) return;
|
|
500
|
+
const start = textarea.selectionStart;
|
|
501
|
+
const end = textarea.selectionEnd;
|
|
502
|
+
const selected = value.slice(start, end) || "texto";
|
|
503
|
+
const next = value.slice(0, start) + marker + selected + marker + value.slice(end);
|
|
504
|
+
onChange(next);
|
|
505
|
+
requestAnimationFrame(() => {
|
|
506
|
+
textarea.focus();
|
|
507
|
+
textarea.setSelectionRange(start + marker.length, start + marker.length + selected.length);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function insertAtCursor(snippet) {
|
|
511
|
+
const textarea = textareaRef.current;
|
|
512
|
+
if (!textarea) return;
|
|
513
|
+
const start = textarea.selectionStart;
|
|
514
|
+
const end = textarea.selectionEnd;
|
|
515
|
+
const next = value.slice(0, start) + snippet + value.slice(end);
|
|
516
|
+
onChange(next);
|
|
517
|
+
requestAnimationFrame(() => {
|
|
518
|
+
textarea.focus();
|
|
519
|
+
const caret = start + snippet.length;
|
|
520
|
+
textarea.setSelectionRange(caret, caret);
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
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";
|
|
524
|
+
return /* @__PURE__ */ jsxs3("div", { children: [
|
|
525
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-1.5 mb-2", children: [
|
|
526
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: () => wrapSelection("*"), className: toolbarButtonClass, title: editorLabels.boldHint, "aria-label": editorLabels.bold, children: /* @__PURE__ */ jsx3(Bold2, { size: 15 }) }),
|
|
527
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: () => wrapSelection("_"), className: toolbarButtonClass, title: editorLabels.italicHint, "aria-label": editorLabels.italic, children: /* @__PURE__ */ jsx3(Italic2, { size: 15 }) }),
|
|
528
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: () => wrapSelection("~"), className: toolbarButtonClass, title: editorLabels.strikethroughHint, "aria-label": editorLabels.strikethrough, children: /* @__PURE__ */ jsx3(Strikethrough2, { size: 15 }) }),
|
|
529
|
+
placeholders.length > 0 && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
530
|
+
/* @__PURE__ */ jsx3("span", { className: "mx-1 h-5 w-px bg-gray-200 dark:bg-gray-600" }),
|
|
531
|
+
placeholders.map((token) => /* @__PURE__ */ jsx3(
|
|
532
|
+
"button",
|
|
533
|
+
{
|
|
534
|
+
type: "button",
|
|
535
|
+
onClick: () => insertAtCursor(token),
|
|
536
|
+
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",
|
|
537
|
+
title: editorLabels.insertPlaceholder(token),
|
|
538
|
+
children: token
|
|
539
|
+
},
|
|
540
|
+
token
|
|
541
|
+
))
|
|
542
|
+
] })
|
|
543
|
+
] }),
|
|
544
|
+
/* @__PURE__ */ jsx3(
|
|
545
|
+
"textarea",
|
|
546
|
+
{
|
|
547
|
+
ref: textareaRef,
|
|
548
|
+
value,
|
|
549
|
+
onChange: (event) => onChange(event.target.value),
|
|
550
|
+
placeholder,
|
|
551
|
+
rows,
|
|
552
|
+
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"
|
|
553
|
+
}
|
|
554
|
+
),
|
|
555
|
+
/* @__PURE__ */ jsxs3("div", { className: "mt-2", children: [
|
|
556
|
+
/* @__PURE__ */ jsx3("p", { className: "text-xs text-gray-500 dark:text-gray-400 mb-1", children: previewLabel }),
|
|
557
|
+
/* @__PURE__ */ jsx3("div", { className: "rounded-xl bg-[#e5ded8] dark:bg-gray-900 p-3", children: /* @__PURE__ */ jsx3("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__ */ jsx3("span", { className: "text-gray-400 dark:text-gray-500", children: emptyPreviewText }) }) })
|
|
558
|
+
] })
|
|
559
|
+
] });
|
|
900
560
|
}
|
|
901
561
|
|
|
902
562
|
// src/Avatar.tsx
|
|
903
|
-
import { jsx as
|
|
563
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
564
|
+
var DEFAULT_AVATAR_LABELS = {
|
|
565
|
+
unnamedContact: "Contato sem nome"
|
|
566
|
+
};
|
|
904
567
|
var sizeClasses = {
|
|
905
568
|
sm: "w-6 h-6 text-xs",
|
|
906
569
|
md: "w-8 h-8 text-sm",
|
|
@@ -926,10 +589,11 @@ function getBgColor(name) {
|
|
|
926
589
|
const hash = name.charCodeAt(0) + (name.charCodeAt(name.length - 1) || 0);
|
|
927
590
|
return bgColors[hash % bgColors.length];
|
|
928
591
|
}
|
|
929
|
-
function Avatar({ name, avatarUrl, size = "md", className = "" }) {
|
|
592
|
+
function Avatar({ name, avatarUrl, size = "md", className = "", labels }) {
|
|
930
593
|
const sizeClass = sizeClasses[size];
|
|
594
|
+
const unnamedContactLabel = labels?.unnamedContact ?? DEFAULT_AVATAR_LABELS.unnamedContact;
|
|
931
595
|
if (avatarUrl) {
|
|
932
|
-
return /* @__PURE__ */
|
|
596
|
+
return /* @__PURE__ */ jsx4(
|
|
933
597
|
"img",
|
|
934
598
|
{
|
|
935
599
|
src: avatarUrl,
|
|
@@ -938,14 +602,24 @@ function Avatar({ name, avatarUrl, size = "md", className = "" }) {
|
|
|
938
602
|
}
|
|
939
603
|
);
|
|
940
604
|
}
|
|
941
|
-
const initials = getInitials(name);
|
|
942
605
|
const bgColor = getBgColor(name);
|
|
943
|
-
|
|
606
|
+
if (!name) {
|
|
607
|
+
return /* @__PURE__ */ jsx4(
|
|
608
|
+
"div",
|
|
609
|
+
{
|
|
610
|
+
className: `${sizeClass} ${bgColor} rounded-full flex items-center justify-center text-white flex-shrink-0 ${className}`,
|
|
611
|
+
role: "img",
|
|
612
|
+
"aria-label": unnamedContactLabel,
|
|
613
|
+
children: /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 24 24", fill: "currentColor", className: "h-[60%] w-[60%]", "aria-hidden": true, children: /* @__PURE__ */ jsx4("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" }) })
|
|
614
|
+
}
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
return /* @__PURE__ */ jsx4(
|
|
944
618
|
"div",
|
|
945
619
|
{
|
|
946
620
|
className: `${sizeClass} ${bgColor} rounded-full flex items-center justify-center text-white font-semibold flex-shrink-0 ${className}`,
|
|
947
|
-
title: name
|
|
948
|
-
children:
|
|
621
|
+
title: name,
|
|
622
|
+
children: getInitials(name)
|
|
949
623
|
}
|
|
950
624
|
);
|
|
951
625
|
}
|
|
@@ -953,30 +627,91 @@ function Avatar({ name, avatarUrl, size = "md", className = "" }) {
|
|
|
953
627
|
// src/ConversationListItem.tsx
|
|
954
628
|
import { useMemo } from "react";
|
|
955
629
|
|
|
956
|
-
// src/
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
630
|
+
// src/conversationChannel.ts
|
|
631
|
+
var CONVERSATION_CHANNEL = {
|
|
632
|
+
WHATSAPP: "whatsapp",
|
|
633
|
+
MESSENGER: "messenger",
|
|
634
|
+
INSTAGRAM: "instagram",
|
|
635
|
+
WEBCHAT: "webchat"
|
|
636
|
+
};
|
|
637
|
+
var DEFAULT_CONVERSATION_CHANNEL = CONVERSATION_CHANNEL.WHATSAPP;
|
|
638
|
+
var REOPEN_MECHANISM = {
|
|
639
|
+
TEMPLATE: "template",
|
|
640
|
+
TAG: "tag",
|
|
641
|
+
NONE: "none"
|
|
642
|
+
};
|
|
643
|
+
var HANDLE_KIND = {
|
|
644
|
+
PHONE: "phone",
|
|
645
|
+
USERNAME: "username",
|
|
646
|
+
SESSION: "session"
|
|
647
|
+
};
|
|
648
|
+
var CHANNEL_CAPABILITIES = {
|
|
649
|
+
[CONVERSATION_CHANNEL.WHATSAPP]: {
|
|
650
|
+
label: "WhatsApp",
|
|
651
|
+
icon: "\u{1F4AC}",
|
|
652
|
+
hasSessionWindow: true,
|
|
653
|
+
windowHours: 24,
|
|
654
|
+
reopenMechanism: REOPEN_MECHANISM.TEMPLATE,
|
|
655
|
+
handleKind: HANDLE_KIND.PHONE
|
|
656
|
+
},
|
|
657
|
+
[CONVERSATION_CHANNEL.MESSENGER]: {
|
|
658
|
+
// Messenger também tem 24h, mas reabre com message tag — não com template aprovado.
|
|
659
|
+
label: "Messenger",
|
|
660
|
+
icon: "\u{1F4E8}",
|
|
661
|
+
hasSessionWindow: true,
|
|
662
|
+
windowHours: 24,
|
|
663
|
+
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
664
|
+
handleKind: HANDLE_KIND.USERNAME
|
|
665
|
+
},
|
|
666
|
+
[CONVERSATION_CHANNEL.INSTAGRAM]: {
|
|
667
|
+
label: "Instagram",
|
|
668
|
+
icon: "\u{1F4F7}",
|
|
669
|
+
hasSessionWindow: true,
|
|
670
|
+
windowHours: 24,
|
|
671
|
+
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
672
|
+
handleKind: HANDLE_KIND.USERNAME
|
|
673
|
+
},
|
|
674
|
+
[CONVERSATION_CHANNEL.WEBCHAT]: {
|
|
675
|
+
// Chat próprio: sem intermediário, sem janela. Bloquear o composer aqui seria inventar limite.
|
|
676
|
+
label: "Chat do site",
|
|
677
|
+
icon: "\u{1F310}",
|
|
678
|
+
hasSessionWindow: false,
|
|
679
|
+
windowHours: 0,
|
|
680
|
+
reopenMechanism: REOPEN_MECHANISM.NONE,
|
|
681
|
+
handleKind: HANDLE_KIND.SESSION
|
|
970
682
|
}
|
|
971
|
-
|
|
683
|
+
};
|
|
684
|
+
function capabilitiesOf(channel) {
|
|
685
|
+
return CHANNEL_CAPABILITIES[channel ?? DEFAULT_CONVERSATION_CHANNEL];
|
|
972
686
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
687
|
+
var CHANNEL_FILTER_ALL = "all";
|
|
688
|
+
function channelFiltersFor(conversations) {
|
|
689
|
+
const present = new Set(
|
|
690
|
+
conversations.map((conversation) => conversation.channel ?? DEFAULT_CONVERSATION_CHANNEL)
|
|
691
|
+
);
|
|
692
|
+
if (present.size < 2) return [];
|
|
693
|
+
const ordered = Object.keys(CHANNEL_CAPABILITIES).filter((channel) => present.has(channel));
|
|
694
|
+
return [
|
|
695
|
+
{ value: CHANNEL_FILTER_ALL, label: "Todos" },
|
|
696
|
+
...ordered.map((channel) => ({ value: channel, label: CHANNEL_CAPABILITIES[channel].label }))
|
|
697
|
+
];
|
|
698
|
+
}
|
|
699
|
+
function formatContactHandle(params) {
|
|
700
|
+
const { handleKind } = capabilitiesOf(params.channel);
|
|
701
|
+
if (handleKind === HANDLE_KIND.PHONE) return formatPhone(params.handle);
|
|
702
|
+
if (handleKind === HANDLE_KIND.USERNAME) return params.handle.startsWith("@") ? params.handle : `@${params.handle}`;
|
|
703
|
+
return `Visitante ${params.handle.slice(-6)}`;
|
|
704
|
+
}
|
|
705
|
+
function contactFlag(params) {
|
|
706
|
+
return capabilitiesOf(params.channel).handleKind === HANDLE_KIND.PHONE ? phoneCountryFlag(params.handle) : "";
|
|
976
707
|
}
|
|
977
708
|
|
|
978
709
|
// src/ConversationListItem.tsx
|
|
979
|
-
import { jsx as
|
|
710
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
711
|
+
var DEFAULT_CONVERSATION_LIST_ITEM_LABELS = {
|
|
712
|
+
expiredWindow: "Janela expirada",
|
|
713
|
+
warningWindow: "Janela pr\xF3xima do fim"
|
|
714
|
+
};
|
|
980
715
|
function formatRelativeTime(dateStr) {
|
|
981
716
|
try {
|
|
982
717
|
const date = new Date(dateStr);
|
|
@@ -1035,50 +770,63 @@ var ConversationListItem = ({
|
|
|
1035
770
|
active = false,
|
|
1036
771
|
selected = false,
|
|
1037
772
|
onClick,
|
|
1038
|
-
onSelect
|
|
773
|
+
onSelect,
|
|
774
|
+
showDivider = true,
|
|
775
|
+
highlightActive = true,
|
|
776
|
+
labels
|
|
1039
777
|
}) => {
|
|
778
|
+
const expiredWindowLabel = labels?.expiredWindow ?? DEFAULT_CONVERSATION_LIST_ITEM_LABELS.expiredWindow;
|
|
779
|
+
const warningWindowLabel = labels?.warningWindow ?? DEFAULT_CONVERSATION_LIST_ITEM_LABELS.warningWindow;
|
|
1040
780
|
const isActive = active || selected;
|
|
1041
781
|
const windowStatus = useMemo(() => getWindowStatus(conversation.lastInboundAt), [conversation.lastInboundAt]);
|
|
1042
782
|
const preview = useMemo(() => lastMessagePreview(conversation), [conversation.lastContent]);
|
|
1043
|
-
const
|
|
783
|
+
const handle = conversation.contactId ?? conversation.whatsappNumber;
|
|
784
|
+
const displayHandle = formatContactHandle({ handle, channel: conversation.channel });
|
|
785
|
+
const name = conversation.clientName || displayHandle || handle;
|
|
1044
786
|
const timestamp = formatRelativeTime(conversation.lastAt);
|
|
787
|
+
const flag = contactFlag({ handle, channel: conversation.channel });
|
|
1045
788
|
const isInbound = conversation.lastDirection === "inbound";
|
|
1046
789
|
const handleClick = () => {
|
|
1047
790
|
onClick?.();
|
|
1048
791
|
onSelect?.(conversation.id);
|
|
1049
792
|
};
|
|
1050
|
-
return /* @__PURE__ */
|
|
793
|
+
return /* @__PURE__ */ jsxs4(
|
|
1051
794
|
"button",
|
|
1052
795
|
{
|
|
1053
796
|
onClick: handleClick,
|
|
1054
|
-
className:
|
|
797
|
+
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
798
|
style: {
|
|
1056
|
-
backgroundColor: isActive ? "#f0f2f5" : "transparent"
|
|
799
|
+
backgroundColor: isActive && highlightActive ? "#f0f2f5" : "transparent"
|
|
1057
800
|
},
|
|
1058
801
|
children: [
|
|
1059
|
-
/* @__PURE__ */
|
|
1060
|
-
/* @__PURE__ */
|
|
1061
|
-
conversation.waitingHuman && /* @__PURE__ */
|
|
802
|
+
/* @__PURE__ */ jsxs4("div", { className: "relative flex-shrink-0", children: [
|
|
803
|
+
/* @__PURE__ */ jsx5(Avatar, { name: conversation.clientName, size: "lg" }),
|
|
804
|
+
conversation.waitingHuman && /* @__PURE__ */ jsx5("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
805
|
] }),
|
|
1063
|
-
/* @__PURE__ */
|
|
1064
|
-
/* @__PURE__ */
|
|
1065
|
-
/* @__PURE__ */
|
|
1066
|
-
/* @__PURE__ */
|
|
1067
|
-
|
|
1068
|
-
windowStatus && windowStatus.label === "
|
|
806
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex-1 min-w-0", children: [
|
|
807
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-2", children: [
|
|
808
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5 min-w-0", children: [
|
|
809
|
+
!conversation.clientName && flag ? /* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: flag }) : null,
|
|
810
|
+
/* @__PURE__ */ jsx5("span", { className: "text-[16px] text-[#111b21] truncate", children: name }),
|
|
811
|
+
windowStatus && windowStatus.label === "expired" && /* @__PURE__ */ jsx5("span", { className: "w-2 h-2 rounded-full bg-red-500 flex-shrink-0", title: expiredWindowLabel }),
|
|
812
|
+
windowStatus && windowStatus.label === "warning" && /* @__PURE__ */ jsx5("span", { className: "w-2 h-2 rounded-full bg-orange-500 flex-shrink-0", title: warningWindowLabel })
|
|
1069
813
|
] }),
|
|
1070
|
-
timestamp && /* @__PURE__ */
|
|
814
|
+
timestamp && /* @__PURE__ */ jsx5("span", { className: "text-xs text-[#667781] flex-shrink-0", children: timestamp })
|
|
1071
815
|
] }),
|
|
1072
|
-
/* @__PURE__ */
|
|
1073
|
-
/* @__PURE__ */
|
|
1074
|
-
|
|
816
|
+
conversation.clientName ? /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1 text-xs text-[#667781]", children: [
|
|
817
|
+
flag ? /* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: flag }) : null,
|
|
818
|
+
/* @__PURE__ */ jsx5("span", { className: "truncate", children: displayHandle })
|
|
819
|
+
] }) : null,
|
|
820
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between mt-0.5", children: [
|
|
821
|
+
/* @__PURE__ */ jsxs4("span", { className: "text-sm text-[#667781] truncate max-w-[180px]", children: [
|
|
822
|
+
preview.icon && /* @__PURE__ */ jsx5("span", { className: "mr-1", children: preview.icon }),
|
|
1075
823
|
preview.preview || "Sem mensagens"
|
|
1076
824
|
] }),
|
|
1077
|
-
conversation.unread > 0 && /* @__PURE__ */
|
|
825
|
+
conversation.unread > 0 && /* @__PURE__ */ jsx5("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
826
|
] }),
|
|
1079
|
-
/* @__PURE__ */
|
|
1080
|
-
conversation.mode === "human" && /* @__PURE__ */
|
|
1081
|
-
conversation.mode === "bot" && conversation.waitingHuman && /* @__PURE__ */
|
|
827
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5 mt-1", children: [
|
|
828
|
+
conversation.mode === "human" && /* @__PURE__ */ jsx5("span", { className: "text-[10px] px-1.5 py-0.5 rounded font-medium bg-purple-100 text-purple-700", children: "Humano" }),
|
|
829
|
+
conversation.mode === "bot" && conversation.waitingHuman && /* @__PURE__ */ jsx5("span", { className: "text-[10px] px-1.5 py-0.5 rounded font-medium bg-amber-100 text-amber-700", children: "Aguardando" })
|
|
1082
830
|
] })
|
|
1083
831
|
] })
|
|
1084
832
|
]
|
|
@@ -1087,9 +835,9 @@ var ConversationListItem = ({
|
|
|
1087
835
|
};
|
|
1088
836
|
|
|
1089
837
|
// src/Toast.tsx
|
|
1090
|
-
import { createContext
|
|
1091
|
-
import { jsx as
|
|
1092
|
-
var ToastContext =
|
|
838
|
+
import { createContext, useContext, useState as useState3, useCallback as useCallback2, useEffect as useEffect3 } from "react";
|
|
839
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
840
|
+
var ToastContext = createContext(null);
|
|
1093
841
|
var nextId = 0;
|
|
1094
842
|
var singletonShow = null;
|
|
1095
843
|
var toast = {
|
|
@@ -1105,19 +853,19 @@ var typeStyles = {
|
|
|
1105
853
|
info: "bg-blue-600"
|
|
1106
854
|
};
|
|
1107
855
|
var typeIcons = {
|
|
1108
|
-
success: /* @__PURE__ */
|
|
1109
|
-
error: /* @__PURE__ */
|
|
1110
|
-
/* @__PURE__ */
|
|
1111
|
-
/* @__PURE__ */
|
|
856
|
+
success: /* @__PURE__ */ jsx6("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx6("polyline", { points: "20 6 9 17 4 12" }) }),
|
|
857
|
+
error: /* @__PURE__ */ jsxs5("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
858
|
+
/* @__PURE__ */ jsx6("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
859
|
+
/* @__PURE__ */ jsx6("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1112
860
|
] }),
|
|
1113
|
-
info: /* @__PURE__ */
|
|
1114
|
-
/* @__PURE__ */
|
|
1115
|
-
/* @__PURE__ */
|
|
1116
|
-
/* @__PURE__ */
|
|
861
|
+
info: /* @__PURE__ */ jsxs5("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
862
|
+
/* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "10" }),
|
|
863
|
+
/* @__PURE__ */ jsx6("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
|
|
864
|
+
/* @__PURE__ */ jsx6("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
|
|
1117
865
|
] })
|
|
1118
866
|
};
|
|
1119
867
|
function ToastItemComponent({ item, onRemove }) {
|
|
1120
|
-
const [exiting, setExiting] =
|
|
868
|
+
const [exiting, setExiting] = useState3(false);
|
|
1121
869
|
useEffect3(() => {
|
|
1122
870
|
const timer = setTimeout(() => {
|
|
1123
871
|
setExiting(true);
|
|
@@ -1125,14 +873,14 @@ function ToastItemComponent({ item, onRemove }) {
|
|
|
1125
873
|
}, 5e3);
|
|
1126
874
|
return () => clearTimeout(timer);
|
|
1127
875
|
}, [item.id, onRemove]);
|
|
1128
|
-
return /* @__PURE__ */
|
|
876
|
+
return /* @__PURE__ */ jsxs5(
|
|
1129
877
|
"div",
|
|
1130
878
|
{
|
|
1131
879
|
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
880
|
children: [
|
|
1133
|
-
/* @__PURE__ */
|
|
1134
|
-
/* @__PURE__ */
|
|
1135
|
-
/* @__PURE__ */
|
|
881
|
+
/* @__PURE__ */ jsx6("span", { className: "flex-shrink-0", children: typeIcons[item.type] }),
|
|
882
|
+
/* @__PURE__ */ jsx6("span", { className: "flex-1", children: item.message }),
|
|
883
|
+
/* @__PURE__ */ jsx6(
|
|
1136
884
|
"button",
|
|
1137
885
|
{
|
|
1138
886
|
onClick: () => {
|
|
@@ -1140,9 +888,9 @@ function ToastItemComponent({ item, onRemove }) {
|
|
|
1140
888
|
setTimeout(() => onRemove(item.id), 300);
|
|
1141
889
|
},
|
|
1142
890
|
className: "flex-shrink-0 opacity-70 hover:opacity-100 transition-opacity",
|
|
1143
|
-
children: /* @__PURE__ */
|
|
1144
|
-
/* @__PURE__ */
|
|
1145
|
-
/* @__PURE__ */
|
|
891
|
+
children: /* @__PURE__ */ jsxs5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
892
|
+
/* @__PURE__ */ jsx6("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
893
|
+
/* @__PURE__ */ jsx6("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1146
894
|
] })
|
|
1147
895
|
}
|
|
1148
896
|
)
|
|
@@ -1151,12 +899,12 @@ function ToastItemComponent({ item, onRemove }) {
|
|
|
1151
899
|
);
|
|
1152
900
|
}
|
|
1153
901
|
function ToastProvider({ children }) {
|
|
1154
|
-
const [items, setItems] =
|
|
1155
|
-
const show =
|
|
902
|
+
const [items, setItems] = useState3([]);
|
|
903
|
+
const show = useCallback2((type, message) => {
|
|
1156
904
|
const id = ++nextId;
|
|
1157
905
|
setItems((prev) => [...prev, { id, type, message }]);
|
|
1158
906
|
}, []);
|
|
1159
|
-
const remove =
|
|
907
|
+
const remove = useCallback2((id) => {
|
|
1160
908
|
setItems((prev) => prev.filter((item) => item.id !== id));
|
|
1161
909
|
}, []);
|
|
1162
910
|
useEffect3(() => {
|
|
@@ -1165,10 +913,10 @@ function ToastProvider({ children }) {
|
|
|
1165
913
|
singletonShow = null;
|
|
1166
914
|
};
|
|
1167
915
|
}, [show]);
|
|
1168
|
-
return /* @__PURE__ */
|
|
916
|
+
return /* @__PURE__ */ jsxs5(ToastContext.Provider, { value: { show }, children: [
|
|
1169
917
|
children,
|
|
1170
|
-
/* @__PURE__ */
|
|
1171
|
-
/* @__PURE__ */
|
|
918
|
+
/* @__PURE__ */ jsx6("div", { className: "fixed top-4 right-4 z-[100] flex flex-col gap-2", children: items.map((item) => /* @__PURE__ */ jsx6(ToastItemComponent, { item, onRemove: remove }, item.id)) }),
|
|
919
|
+
/* @__PURE__ */ jsx6("style", { children: `
|
|
1172
920
|
@keyframes slide-in {
|
|
1173
921
|
from { transform: translateX(100%); opacity: 0; }
|
|
1174
922
|
to { transform: translateX(0); opacity: 1; }
|
|
@@ -1183,124 +931,727 @@ function ToastProvider({ children }) {
|
|
|
1183
931
|
] });
|
|
1184
932
|
}
|
|
1185
933
|
function useToast() {
|
|
1186
|
-
const context =
|
|
934
|
+
const context = useContext(ToastContext);
|
|
1187
935
|
if (!context) {
|
|
1188
936
|
throw new Error("useToast must be used within a ToastProvider");
|
|
1189
937
|
}
|
|
1190
|
-
return context;
|
|
938
|
+
return context;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// src/MessageText.tsx
|
|
942
|
+
import { useCallback as useCallback3, useState as useState4 } from "react";
|
|
943
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
944
|
+
function MessageText({ message }) {
|
|
945
|
+
const [copied, setCopied] = useState4(false);
|
|
946
|
+
const handleCopy = useCallback3(async () => {
|
|
947
|
+
if (!message.content) return;
|
|
948
|
+
try {
|
|
949
|
+
await navigator.clipboard.writeText(message.content);
|
|
950
|
+
setCopied(true);
|
|
951
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
952
|
+
} catch {
|
|
953
|
+
}
|
|
954
|
+
}, [message.content]);
|
|
955
|
+
if (message.type === "template")
|
|
956
|
+
return /* @__PURE__ */ jsx7("p", { className: "text-sm italic text-[#667781]", children: message.content ?? "Template message" });
|
|
957
|
+
return /* @__PURE__ */ jsxs6(
|
|
958
|
+
"div",
|
|
959
|
+
{
|
|
960
|
+
onClick: handleCopy,
|
|
961
|
+
className: "text-[14.2px] leading-[19px] whitespace-pre-wrap break-words select-all [&_strong]:font-bold [&_em]:italic [&_del]:line-through",
|
|
962
|
+
children: [
|
|
963
|
+
/* @__PURE__ */ jsx7("span", { children: parseWhatsAppFormatting(message.content ?? "") }),
|
|
964
|
+
copied && /* @__PURE__ */ jsx7("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!" })
|
|
965
|
+
]
|
|
966
|
+
}
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/MessageTimestamp.tsx
|
|
971
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
972
|
+
function MessageTimestamp({ timestamp, status, isOutbound }) {
|
|
973
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-end gap-[3px]", children: [
|
|
974
|
+
/* @__PURE__ */ jsx8("span", { className: "text-[11px] text-[#667781] leading-[15px] select-none", children: timestamp }),
|
|
975
|
+
isOutbound && status && /* @__PURE__ */ jsx8("span", { className: "flex items-center -mr-[2px]", children: /* @__PURE__ */ jsx8(StatusTicks, { status }) })
|
|
976
|
+
] });
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/MessageTail.tsx
|
|
980
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
981
|
+
function MessageTail({ isOutbound }) {
|
|
982
|
+
if (isOutbound)
|
|
983
|
+
return /* @__PURE__ */ jsx9("svg", { className: "absolute top-0 -right-[5px] w-[5px] h-[11px]", viewBox: "0 0 5 11", fill: "#d9fdd3", children: /* @__PURE__ */ jsx9("path", { d: "M5 0H0v11c1.5-2 4.5-2 5-4V0z" }) });
|
|
984
|
+
return /* @__PURE__ */ jsx9("svg", { className: "absolute top-0 -left-[5px] w-[5px] h-[11px]", viewBox: "0 0 5 11", fill: "white", children: /* @__PURE__ */ jsx9("path", { d: "M0 0h5v11C3.5 9 .5 9 0 7V0z" }) });
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/conversationWindow.ts
|
|
988
|
+
var MINUTE_MS = 6e4;
|
|
989
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
990
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
991
|
+
var CONVERSATION_WINDOW = {
|
|
992
|
+
ALL: "all",
|
|
993
|
+
FRESH: "fresh",
|
|
994
|
+
WARNING: "warning",
|
|
995
|
+
CRITICAL: "critical",
|
|
996
|
+
EXPIRED: "expired"
|
|
997
|
+
};
|
|
998
|
+
var WINDOW_FILTERS = [
|
|
999
|
+
{ value: CONVERSATION_WINDOW.ALL, label: "Todas", dotClass: "" },
|
|
1000
|
+
{ value: CONVERSATION_WINDOW.FRESH, label: "<12h", dotClass: "bg-green-500" },
|
|
1001
|
+
{ value: CONVERSATION_WINDOW.WARNING, label: "12-21h", dotClass: "bg-yellow-500" },
|
|
1002
|
+
{ value: CONVERSATION_WINDOW.CRITICAL, label: "21-24h", dotClass: "bg-red-500" },
|
|
1003
|
+
{ value: CONVERSATION_WINDOW.EXPIRED, label: ">24h", dotClass: "bg-gray-400" }
|
|
1004
|
+
];
|
|
1005
|
+
var WARNING_RATIO = 0.5;
|
|
1006
|
+
var CRITICAL_RATIO = 0.875;
|
|
1007
|
+
function windowOf(params) {
|
|
1008
|
+
const capabilities = capabilitiesOf(params.channel);
|
|
1009
|
+
if (!capabilities.hasSessionWindow) return CONVERSATION_WINDOW.FRESH;
|
|
1010
|
+
if (!params.lastInboundAt) return CONVERSATION_WINDOW.EXPIRED;
|
|
1011
|
+
const windowMs = capabilities.windowHours * HOUR_MS;
|
|
1012
|
+
const elapsed = params.now - new Date(params.lastInboundAt).getTime();
|
|
1013
|
+
if (elapsed >= windowMs) return CONVERSATION_WINDOW.EXPIRED;
|
|
1014
|
+
if (elapsed >= windowMs * CRITICAL_RATIO) return CONVERSATION_WINDOW.CRITICAL;
|
|
1015
|
+
if (elapsed >= windowMs * WARNING_RATIO) return CONVERSATION_WINDOW.WARNING;
|
|
1016
|
+
return CONVERSATION_WINDOW.FRESH;
|
|
1017
|
+
}
|
|
1018
|
+
function formatStalledFor(lastAt, now) {
|
|
1019
|
+
const elapsed = Math.max(0, now - new Date(lastAt).getTime());
|
|
1020
|
+
const days = Math.floor(elapsed / DAY_MS);
|
|
1021
|
+
const hours = Math.floor(elapsed % DAY_MS / HOUR_MS);
|
|
1022
|
+
const minutes = Math.floor(elapsed % HOUR_MS / MINUTE_MS);
|
|
1023
|
+
if (days > 0) return `${days}d ${hours}h:${String(minutes).padStart(2, "0")}m`;
|
|
1024
|
+
if (hours > 0) return `${hours}h:${String(minutes).padStart(2, "0")}m`;
|
|
1025
|
+
return `${minutes}m`;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/ChannelIcon.tsx
|
|
1029
|
+
import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1030
|
+
var CHANNEL_BRAND_COLOR = {
|
|
1031
|
+
[CONVERSATION_CHANNEL.WHATSAPP]: "#25D366",
|
|
1032
|
+
[CONVERSATION_CHANNEL.MESSENGER]: "#0084FF",
|
|
1033
|
+
[CONVERSATION_CHANNEL.INSTAGRAM]: "#E4405F",
|
|
1034
|
+
[CONVERSATION_CHANNEL.WEBCHAT]: "#64748B"
|
|
1035
|
+
};
|
|
1036
|
+
function Glyph({ channel }) {
|
|
1037
|
+
const color = CHANNEL_BRAND_COLOR[channel];
|
|
1038
|
+
if (channel === CONVERSATION_CHANNEL.WHATSAPP) {
|
|
1039
|
+
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1040
|
+
/* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
1041
|
+
/* @__PURE__ */ jsx10(
|
|
1042
|
+
"path",
|
|
1043
|
+
{
|
|
1044
|
+
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",
|
|
1045
|
+
fill: "#fff"
|
|
1046
|
+
}
|
|
1047
|
+
)
|
|
1048
|
+
] });
|
|
1049
|
+
}
|
|
1050
|
+
if (channel === CONVERSATION_CHANNEL.MESSENGER) {
|
|
1051
|
+
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1052
|
+
/* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
1053
|
+
/* @__PURE__ */ jsx10("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" })
|
|
1054
|
+
] });
|
|
1055
|
+
}
|
|
1056
|
+
if (channel === CONVERSATION_CHANNEL.INSTAGRAM) {
|
|
1057
|
+
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1058
|
+
/* @__PURE__ */ jsx10("rect", { x: "2", y: "2", width: "20", height: "20", rx: "6", fill: color }),
|
|
1059
|
+
/* @__PURE__ */ jsx10("rect", { x: "6.5", y: "6.5", width: "11", height: "11", rx: "3.5", fill: "none", stroke: "#fff", strokeWidth: "1.6" }),
|
|
1060
|
+
/* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "2.6", fill: "none", stroke: "#fff", strokeWidth: "1.6" }),
|
|
1061
|
+
/* @__PURE__ */ jsx10("circle", { cx: "16.4", cy: "7.6", r: "1", fill: "#fff" })
|
|
1062
|
+
] });
|
|
1063
|
+
}
|
|
1064
|
+
return /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
1065
|
+
/* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "11", fill: color }),
|
|
1066
|
+
/* @__PURE__ */ jsx10("path", { d: "M6.5 8.5h11v7h-5.5L8.5 18v-2.5h-2v-7Z", fill: "#fff" })
|
|
1067
|
+
] });
|
|
1191
1068
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1069
|
+
function ChannelIcon({ channel, size = 14, className = "" }) {
|
|
1070
|
+
const resolved = channel ?? CONVERSATION_CHANNEL.WHATSAPP;
|
|
1071
|
+
const { label } = capabilitiesOf(resolved);
|
|
1072
|
+
return /* @__PURE__ */ jsx10(
|
|
1073
|
+
"svg",
|
|
1074
|
+
{
|
|
1075
|
+
viewBox: "0 0 24 24",
|
|
1076
|
+
width: size,
|
|
1077
|
+
height: size,
|
|
1078
|
+
className: `inline-block shrink-0 ${className}`.trim(),
|
|
1079
|
+
role: "img",
|
|
1080
|
+
"aria-label": label,
|
|
1081
|
+
children: /* @__PURE__ */ jsx10(Glyph, { channel: resolved })
|
|
1205
1082
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/ConversationRow.tsx
|
|
1087
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1088
|
+
var WINDOW_TITLE = {
|
|
1089
|
+
[CONVERSATION_WINDOW.ALL]: "",
|
|
1090
|
+
[CONVERSATION_WINDOW.FRESH]: "Janela de 24h aberta (menos de 12h)",
|
|
1091
|
+
[CONVERSATION_WINDOW.WARNING]: "Janela de 24h fechando (12-21h)",
|
|
1092
|
+
[CONVERSATION_WINDOW.CRITICAL]: "Janela de 24h quase expirada (21-24h)",
|
|
1093
|
+
[CONVERSATION_WINDOW.EXPIRED]: "Janela de 24h expirada \u2014 s\xF3 template"
|
|
1094
|
+
};
|
|
1095
|
+
var STALLED_THRESHOLD_MS = 60 * 60 * 1e3;
|
|
1096
|
+
var WINDOW_BAR_CLASS = {
|
|
1097
|
+
[CONVERSATION_WINDOW.ALL]: "bg-transparent",
|
|
1098
|
+
[CONVERSATION_WINDOW.FRESH]: "bg-green-500",
|
|
1099
|
+
[CONVERSATION_WINDOW.WARNING]: "bg-yellow-500",
|
|
1100
|
+
[CONVERSATION_WINDOW.CRITICAL]: "bg-red-500",
|
|
1101
|
+
[CONVERSATION_WINDOW.EXPIRED]: "bg-gray-300"
|
|
1102
|
+
};
|
|
1103
|
+
function ConversationRow({
|
|
1104
|
+
conversation,
|
|
1105
|
+
active,
|
|
1106
|
+
selected,
|
|
1107
|
+
now,
|
|
1108
|
+
busy,
|
|
1109
|
+
onOpen,
|
|
1110
|
+
onToggleSelected,
|
|
1111
|
+
onTakeover,
|
|
1112
|
+
className,
|
|
1113
|
+
classNames
|
|
1114
|
+
}) {
|
|
1115
|
+
const capabilities = capabilitiesOf(conversation.channel);
|
|
1116
|
+
const window2 = windowOf({ lastInboundAt: conversation.lastInboundAt, now, channel: conversation.channel });
|
|
1117
|
+
const stalledMs = now - new Date(conversation.lastAt).getTime();
|
|
1118
|
+
const isStalled = stalledMs > STALLED_THRESHOLD_MS;
|
|
1119
|
+
const isWaiting = conversation.mode === "bot" && conversation.waitingHuman;
|
|
1120
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn("cv-row flex border-b", active && "cv-row--active", classNames?.root, className), children: [
|
|
1121
|
+
/* @__PURE__ */ jsx11(
|
|
1122
|
+
"span",
|
|
1123
|
+
{
|
|
1124
|
+
className: cn("w-1 shrink-0", WINDOW_BAR_CLASS[window2], classNames?.windowBar),
|
|
1125
|
+
title: WINDOW_TITLE[window2],
|
|
1126
|
+
"aria-label": WINDOW_TITLE[window2]
|
|
1127
|
+
}
|
|
1128
|
+
),
|
|
1129
|
+
/* @__PURE__ */ jsxs9("div", { className: "min-w-0 flex-1", children: [
|
|
1130
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-start gap-2 px-2 pt-2", children: [
|
|
1131
|
+
/* @__PURE__ */ jsx11(
|
|
1132
|
+
"input",
|
|
1133
|
+
{
|
|
1134
|
+
type: "checkbox",
|
|
1135
|
+
checked: selected,
|
|
1136
|
+
onChange: onToggleSelected,
|
|
1137
|
+
"aria-label": `Selecionar conversa ${conversation.whatsappNumber}`,
|
|
1138
|
+
className: "mt-3"
|
|
1139
|
+
}
|
|
1140
|
+
),
|
|
1141
|
+
/* @__PURE__ */ jsx11("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx11(
|
|
1142
|
+
ConversationListItem,
|
|
1143
|
+
{
|
|
1144
|
+
conversation,
|
|
1145
|
+
active,
|
|
1146
|
+
onClick: onOpen,
|
|
1147
|
+
showDivider: false,
|
|
1148
|
+
highlightActive: false
|
|
1149
|
+
}
|
|
1150
|
+
) })
|
|
1151
|
+
] }),
|
|
1152
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap items-center gap-1.5 px-4 pb-2 pl-8", children: [
|
|
1153
|
+
/* @__PURE__ */ jsxs9("span", { className: "cv-pill", children: [
|
|
1154
|
+
/* @__PURE__ */ jsx11(ChannelIcon, { channel: conversation.channel }),
|
|
1155
|
+
" ",
|
|
1156
|
+
capabilities.label
|
|
1157
|
+
] }),
|
|
1158
|
+
conversation.mode === "human" ? /* @__PURE__ */ jsx11("span", { className: "cv-pill cv-pill--success", children: "\u{1F9D1}\u200D\u{1F4BC} em atendimento" }) : null,
|
|
1159
|
+
isWaiting ? /* @__PURE__ */ jsx11("span", { className: "cv-pill cv-pill--warning", children: "\u23F3 aguardando atendimento" }) : null,
|
|
1160
|
+
!isWaiting && conversation.mode === "bot" ? /* @__PURE__ */ jsx11("span", { className: "cv-pill", children: "\u{1F916} bot ativo" }) : null,
|
|
1161
|
+
isStalled ? /* @__PURE__ */ jsxs9("span", { className: "cv-pill cv-pill--danger", children: [
|
|
1162
|
+
"\u23F1\uFE0F parada h\xE1 ",
|
|
1163
|
+
formatStalledFor(conversation.lastAt, now)
|
|
1164
|
+
] }) : null,
|
|
1165
|
+
isWaiting ? /* @__PURE__ */ jsx11("button", { type: "button", onClick: onTakeover, disabled: busy, className: "cv-header-action", children: "\u25B6\uFE0F Continuar Atendimento" }) : null
|
|
1166
|
+
] })
|
|
1167
|
+
] })
|
|
1168
|
+
] });
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// src/ConversationHeader.tsx
|
|
1172
|
+
import { useState as useState5 } from "react";
|
|
1173
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1174
|
+
var DEFAULT_CONVERSATION_HEADER_LABELS = {
|
|
1175
|
+
botMode: "\u{1F916} atendimento autom\xE1tico",
|
|
1176
|
+
humanMode: "\u{1F9D1}\u200D\u{1F4BC} atendimento humano",
|
|
1177
|
+
returnToBot: "Devolver ao bot",
|
|
1178
|
+
finish: "Finalizar",
|
|
1179
|
+
takeover: "Assumir atendimento",
|
|
1180
|
+
download: "Baixar conversa",
|
|
1181
|
+
documents: "Arquivos da conversa",
|
|
1182
|
+
back: "Voltar para a lista",
|
|
1183
|
+
moreActions: "Mais a\xE7\xF5es"
|
|
1184
|
+
};
|
|
1185
|
+
function ConversationHeader({
|
|
1186
|
+
conversation,
|
|
1187
|
+
busy = false,
|
|
1188
|
+
onTakeover,
|
|
1189
|
+
onReturnToBot,
|
|
1190
|
+
onFinish,
|
|
1191
|
+
onDownload,
|
|
1192
|
+
onOpenDocuments,
|
|
1193
|
+
documentsOpen = false,
|
|
1194
|
+
extraUtilities,
|
|
1195
|
+
onBack,
|
|
1196
|
+
labels: labelsOverride,
|
|
1197
|
+
className,
|
|
1198
|
+
classNames
|
|
1199
|
+
}) {
|
|
1200
|
+
const labels = { ...DEFAULT_CONVERSATION_HEADER_LABELS, ...labelsOverride };
|
|
1201
|
+
const isHuman = conversation.mode === "human";
|
|
1202
|
+
const handle = conversation.contactId ?? conversation.whatsappNumber;
|
|
1203
|
+
const displayHandle = formatContactHandle({ handle, channel: conversation.channel });
|
|
1204
|
+
const flag = contactFlag({ handle, channel: conversation.channel });
|
|
1205
|
+
const capabilities = capabilitiesOf(conversation.channel);
|
|
1206
|
+
const [menuOpen, setMenuOpen] = useState5(false);
|
|
1207
|
+
const utilities = [
|
|
1208
|
+
onOpenDocuments ? { key: "documents", icon: "\u{1F4C4}", label: labels.documents, run: onOpenDocuments, active: documentsOpen } : void 0,
|
|
1209
|
+
onDownload ? { key: "download", icon: "\u2B07\uFE0F", label: labels.download, run: onDownload, active: false } : void 0,
|
|
1210
|
+
...(extraUtilities ?? []).map((utility) => ({ ...utility, active: utility.active ?? false }))
|
|
1211
|
+
].filter(
|
|
1212
|
+
(utility) => Boolean(utility)
|
|
1213
|
+
);
|
|
1214
|
+
const actions = [
|
|
1215
|
+
!isHuman && onTakeover ? { key: "takeover", label: labels.takeover, run: onTakeover, className: "cv-header-action--primary" } : void 0,
|
|
1216
|
+
isHuman && onReturnToBot ? { key: "release", label: labels.returnToBot, run: onReturnToBot, className: "" } : void 0,
|
|
1217
|
+
isHuman && onFinish ? { key: "finish", label: `\u2715 ${labels.finish}`, run: onFinish, className: "cv-header-action--danger" } : void 0
|
|
1218
|
+
].filter((action) => Boolean(action));
|
|
1219
|
+
const menuItems = [
|
|
1220
|
+
...utilities.map((utility) => ({
|
|
1221
|
+
key: utility.key,
|
|
1222
|
+
label: `${utility.icon} ${utility.label}`,
|
|
1223
|
+
run: utility.run,
|
|
1224
|
+
className: ""
|
|
1225
|
+
})),
|
|
1226
|
+
...actions
|
|
1227
|
+
];
|
|
1228
|
+
return /* @__PURE__ */ jsxs10(
|
|
1229
|
+
"header",
|
|
1211
1230
|
{
|
|
1212
|
-
|
|
1213
|
-
|
|
1231
|
+
className: cn(
|
|
1232
|
+
"flex flex-nowrap items-center justify-between gap-3 border-b px-4 py-3",
|
|
1233
|
+
classNames?.root,
|
|
1234
|
+
className
|
|
1235
|
+
),
|
|
1214
1236
|
children: [
|
|
1215
|
-
/* @__PURE__ */
|
|
1216
|
-
|
|
1237
|
+
/* @__PURE__ */ jsxs10("div", { className: cn("flex min-w-0 flex-1 items-center gap-3", classNames?.identity), children: [
|
|
1238
|
+
onBack ? /* @__PURE__ */ jsx12("button", { type: "button", onClick: onBack, className: "cv-back cv-touch", "aria-label": labels.back, title: labels.back, children: "\u2190" }) : null,
|
|
1239
|
+
/* @__PURE__ */ jsx12(Avatar, { name: conversation.clientName, size: "md" }),
|
|
1240
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0", children: [
|
|
1241
|
+
/* @__PURE__ */ jsx12("p", { className: cn("truncate font-medium", classNames?.name), children: conversation.clientName ?? displayHandle }),
|
|
1242
|
+
/* @__PURE__ */ jsxs10("p", { className: cn("truncate text-xs text-gray-500", classNames?.meta), children: [
|
|
1243
|
+
flag ? `${flag} ` : "",
|
|
1244
|
+
displayHandle,
|
|
1245
|
+
/* @__PURE__ */ jsxs10("span", { className: "ml-2 inline-flex items-center gap-1", children: [
|
|
1246
|
+
/* @__PURE__ */ jsx12(ChannelIcon, { channel: conversation.channel }),
|
|
1247
|
+
/* @__PURE__ */ jsx12("span", { className: "cv-only-wide", children: capabilities.label })
|
|
1248
|
+
] }),
|
|
1249
|
+
/* @__PURE__ */ jsx12("span", { className: "ml-2 cv-only-wide", children: isHuman ? labels.humanMode : labels.botMode }),
|
|
1250
|
+
/* @__PURE__ */ jsx12("span", { className: "ml-1 cv-only-narrow", title: isHuman ? labels.humanMode : labels.botMode, children: isHuman ? "\u{1F9D1}\u200D\u{1F4BC}" : "\u{1F916}" })
|
|
1251
|
+
] })
|
|
1252
|
+
] })
|
|
1253
|
+
] }),
|
|
1254
|
+
/* @__PURE__ */ jsxs10("div", { className: cn("flex shrink-0 items-center gap-2", classNames?.actions), children: [
|
|
1255
|
+
/* @__PURE__ */ jsxs10("div", { className: cn("hidden items-center gap-2 lg:flex", classNames?.desktopActions), children: [
|
|
1256
|
+
utilities.map((utility) => /* @__PURE__ */ jsx12(
|
|
1257
|
+
"button",
|
|
1258
|
+
{
|
|
1259
|
+
type: "button",
|
|
1260
|
+
onClick: utility.run,
|
|
1261
|
+
disabled: busy,
|
|
1262
|
+
"aria-pressed": utility.active,
|
|
1263
|
+
title: utility.label,
|
|
1264
|
+
"aria-label": utility.label,
|
|
1265
|
+
className: `cv-header-icon ${utility.active ? "cv-header-icon--active" : ""}`,
|
|
1266
|
+
children: utility.icon
|
|
1267
|
+
},
|
|
1268
|
+
utility.key
|
|
1269
|
+
)),
|
|
1270
|
+
actions.map((action) => /* @__PURE__ */ jsx12(
|
|
1271
|
+
"button",
|
|
1272
|
+
{
|
|
1273
|
+
type: "button",
|
|
1274
|
+
onClick: action.run,
|
|
1275
|
+
disabled: busy,
|
|
1276
|
+
className: `cv-header-action ${action.className}`,
|
|
1277
|
+
children: action.label
|
|
1278
|
+
},
|
|
1279
|
+
action.key
|
|
1280
|
+
))
|
|
1281
|
+
] }),
|
|
1282
|
+
menuItems.length > 0 ? /* @__PURE__ */ jsxs10("div", { className: cn("cv-menu lg:hidden", classNames?.mobileMenu), children: [
|
|
1283
|
+
/* @__PURE__ */ jsx12(
|
|
1284
|
+
"button",
|
|
1285
|
+
{
|
|
1286
|
+
type: "button",
|
|
1287
|
+
onClick: () => setMenuOpen(!menuOpen),
|
|
1288
|
+
"aria-expanded": menuOpen,
|
|
1289
|
+
"aria-label": labels.moreActions,
|
|
1290
|
+
title: labels.moreActions,
|
|
1291
|
+
className: "cv-header-icon cv-touch",
|
|
1292
|
+
children: "\u22EE"
|
|
1293
|
+
}
|
|
1294
|
+
),
|
|
1295
|
+
menuOpen ? /* @__PURE__ */ jsx12("div", { className: "cv-menu-panel", role: "menu", children: menuItems.map((item) => /* @__PURE__ */ jsx12(
|
|
1296
|
+
"button",
|
|
1297
|
+
{
|
|
1298
|
+
type: "button",
|
|
1299
|
+
role: "menuitem",
|
|
1300
|
+
onClick: () => {
|
|
1301
|
+
setMenuOpen(false);
|
|
1302
|
+
item.run();
|
|
1303
|
+
},
|
|
1304
|
+
disabled: busy,
|
|
1305
|
+
className: `cv-header-action cv-touch ${item.className}`,
|
|
1306
|
+
children: item.label
|
|
1307
|
+
},
|
|
1308
|
+
item.key
|
|
1309
|
+
)) }) : null
|
|
1310
|
+
] }) : null
|
|
1311
|
+
] })
|
|
1217
1312
|
]
|
|
1218
1313
|
}
|
|
1219
1314
|
);
|
|
1220
1315
|
}
|
|
1221
1316
|
|
|
1222
|
-
// src/
|
|
1223
|
-
import {
|
|
1224
|
-
|
|
1225
|
-
return /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-end gap-[3px]", children: [
|
|
1226
|
-
/* @__PURE__ */ jsx18("span", { className: "text-[11px] text-[#667781] leading-[15px] select-none", children: timestamp }),
|
|
1227
|
-
isOutbound && status && /* @__PURE__ */ jsx18("span", { className: "flex items-center -mr-[2px]", children: /* @__PURE__ */ jsx18(StatusTicks, { status }) })
|
|
1228
|
-
] });
|
|
1229
|
-
}
|
|
1317
|
+
// src/ConversationContextPanel.tsx
|
|
1318
|
+
import { useState as useState7 } from "react";
|
|
1319
|
+
import { Check, ChevronDown, ChevronUp, Clock, Pencil } from "lucide-react";
|
|
1230
1320
|
|
|
1231
|
-
// src/
|
|
1232
|
-
import {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1321
|
+
// src/useIsNarrow.ts
|
|
1322
|
+
import { useEffect as useEffect4, useState as useState6 } from "react";
|
|
1323
|
+
var NARROW_MAX_WIDTH_PX = 1023;
|
|
1324
|
+
function useIsNarrow() {
|
|
1325
|
+
const [isNarrow, setIsNarrow] = useState6(
|
|
1326
|
+
() => typeof window !== "undefined" && window.matchMedia(`(max-width: ${NARROW_MAX_WIDTH_PX}px)`).matches
|
|
1327
|
+
);
|
|
1328
|
+
useEffect4(() => {
|
|
1329
|
+
const query = window.matchMedia(`(max-width: ${NARROW_MAX_WIDTH_PX}px)`);
|
|
1330
|
+
const handleChange = (event) => setIsNarrow(event.matches);
|
|
1331
|
+
query.addEventListener("change", handleChange);
|
|
1332
|
+
setIsNarrow(query.matches);
|
|
1333
|
+
return () => query.removeEventListener("change", handleChange);
|
|
1334
|
+
}, []);
|
|
1335
|
+
return isNarrow;
|
|
1237
1336
|
}
|
|
1238
1337
|
|
|
1239
|
-
// src/
|
|
1240
|
-
import {
|
|
1338
|
+
// src/ConversationContextPanel.tsx
|
|
1339
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1340
|
+
var DEFAULT_CONVERSATION_CONTEXT_LABELS = {
|
|
1341
|
+
title: "Suas Sele\xE7\xF5es",
|
|
1342
|
+
empty: "Nada coletado ainda nesta conversa.",
|
|
1343
|
+
collapse: "fechar",
|
|
1344
|
+
expand: "abrir",
|
|
1345
|
+
notCollected: "a coletar"
|
|
1346
|
+
};
|
|
1347
|
+
function statusOf(entry) {
|
|
1348
|
+
if (entry.status) return entry.status;
|
|
1349
|
+
return entry.value ? "completed" : "pending";
|
|
1350
|
+
}
|
|
1351
|
+
var CARD_STYLE = {
|
|
1352
|
+
completed: "bg-white dark:bg-gray-800 border-2 border-green-200 dark:border-green-800 shadow-sm",
|
|
1353
|
+
editing: "bg-white dark:bg-gray-800 border-2 border-blue-200 dark:border-blue-800 shadow-sm",
|
|
1354
|
+
pending: "border-2 border-dashed border-gray-200 bg-white/40 dark:border-gray-700 dark:bg-gray-800/30"
|
|
1355
|
+
};
|
|
1356
|
+
var GLYPH_STYLE = {
|
|
1357
|
+
completed: "text-green-600 dark:text-green-400",
|
|
1358
|
+
editing: "text-blue-600 dark:text-blue-400",
|
|
1359
|
+
pending: "text-gray-400 dark:text-gray-500"
|
|
1360
|
+
};
|
|
1361
|
+
function StatusGlyph({ status }) {
|
|
1362
|
+
const size = 11;
|
|
1363
|
+
if (status === "completed") return /* @__PURE__ */ jsx13(Check, { size, "aria-hidden": true });
|
|
1364
|
+
if (status === "editing") return /* @__PURE__ */ jsx13(Pencil, { size, "aria-hidden": true });
|
|
1365
|
+
return /* @__PURE__ */ jsx13(Clock, { size, "aria-hidden": true });
|
|
1366
|
+
}
|
|
1367
|
+
function CountPill({
|
|
1368
|
+
status,
|
|
1369
|
+
count,
|
|
1370
|
+
className
|
|
1371
|
+
}) {
|
|
1372
|
+
if (count === 0) return null;
|
|
1373
|
+
return /* @__PURE__ */ jsxs11("span", { className: cn("flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium", className), children: [
|
|
1374
|
+
/* @__PURE__ */ jsx13(StatusGlyph, { status }),
|
|
1375
|
+
count
|
|
1376
|
+
] });
|
|
1377
|
+
}
|
|
1378
|
+
function ConversationContextPanel({
|
|
1379
|
+
entries,
|
|
1380
|
+
defaultOpen,
|
|
1381
|
+
flowLabel,
|
|
1382
|
+
labels: labelsOverride,
|
|
1383
|
+
className,
|
|
1384
|
+
classNames
|
|
1385
|
+
}) {
|
|
1386
|
+
const labels = { ...DEFAULT_CONVERSATION_CONTEXT_LABELS, ...labelsOverride };
|
|
1387
|
+
const counts = { completed: 0, pending: 0, editing: 0 };
|
|
1388
|
+
for (const entry of entries) counts[statusOf(entry)] += 1;
|
|
1389
|
+
const isNarrow = useIsNarrow();
|
|
1390
|
+
const [manualOpen, setManualOpen] = useState7(void 0);
|
|
1391
|
+
const open = manualOpen ?? defaultOpen ?? (!isNarrow && counts.completed > 0);
|
|
1392
|
+
return (
|
|
1393
|
+
// Faixa azul clara com separador, como o wrapper do financiamento: sem ela o card encostava
|
|
1394
|
+
// direto no papel de parede da conversa e os dois blocos se misturavam.
|
|
1395
|
+
/* @__PURE__ */ jsx13("div", { className: cn("border-b bg-blue-50/60 px-3 py-3 dark:border-gray-700 dark:bg-blue-950/20", className), children: /* @__PURE__ */ jsxs11(
|
|
1396
|
+
"section",
|
|
1397
|
+
{
|
|
1398
|
+
className: cn(
|
|
1399
|
+
"overflow-hidden rounded-lg border border-blue-200 bg-gradient-to-br from-blue-50 to-indigo-50 dark:border-blue-900 dark:from-blue-950/30 dark:to-indigo-950/30",
|
|
1400
|
+
classNames?.root
|
|
1401
|
+
),
|
|
1402
|
+
children: [
|
|
1403
|
+
/* @__PURE__ */ jsxs11(
|
|
1404
|
+
"button",
|
|
1405
|
+
{
|
|
1406
|
+
type: "button",
|
|
1407
|
+
onClick: () => setManualOpen(!open),
|
|
1408
|
+
"aria-expanded": open,
|
|
1409
|
+
title: open ? labels.collapse : labels.expand,
|
|
1410
|
+
className: cn(
|
|
1411
|
+
"flex w-full cursor-pointer items-center justify-between gap-2 px-4 py-2.5 text-left transition-colors hover:bg-blue-100/40 dark:hover:bg-blue-900/20",
|
|
1412
|
+
classNames?.toggle
|
|
1413
|
+
),
|
|
1414
|
+
children: [
|
|
1415
|
+
/* @__PURE__ */ jsxs11("span", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1416
|
+
/* @__PURE__ */ jsx13("span", { "aria-hidden": true, className: "text-base leading-none", children: "\u{1F4CB}" }),
|
|
1417
|
+
/* @__PURE__ */ jsx13("span", { className: "truncate text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.title }),
|
|
1418
|
+
flowLabel ? /* @__PURE__ */ jsx13("span", { className: "truncate text-xs font-medium text-blue-600 dark:text-blue-400", children: flowLabel }) : null,
|
|
1419
|
+
/* @__PURE__ */ jsx13(
|
|
1420
|
+
"span",
|
|
1421
|
+
{
|
|
1422
|
+
className: cn(
|
|
1423
|
+
"inline-flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-blue-600 text-xs font-medium text-white",
|
|
1424
|
+
classNames?.counter
|
|
1425
|
+
),
|
|
1426
|
+
children: entries.length
|
|
1427
|
+
}
|
|
1428
|
+
)
|
|
1429
|
+
] }),
|
|
1430
|
+
/* @__PURE__ */ jsxs11("span", { className: "flex flex-shrink-0 items-center gap-2", children: [
|
|
1431
|
+
/* @__PURE__ */ jsx13(
|
|
1432
|
+
CountPill,
|
|
1433
|
+
{
|
|
1434
|
+
status: "completed",
|
|
1435
|
+
count: counts.completed,
|
|
1436
|
+
className: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
|
|
1437
|
+
}
|
|
1438
|
+
),
|
|
1439
|
+
/* @__PURE__ */ jsx13(
|
|
1440
|
+
CountPill,
|
|
1441
|
+
{
|
|
1442
|
+
status: "editing",
|
|
1443
|
+
count: counts.editing,
|
|
1444
|
+
className: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
|
1445
|
+
}
|
|
1446
|
+
),
|
|
1447
|
+
/* @__PURE__ */ jsx13(
|
|
1448
|
+
CountPill,
|
|
1449
|
+
{
|
|
1450
|
+
status: "pending",
|
|
1451
|
+
count: counts.pending,
|
|
1452
|
+
className: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
|
1453
|
+
}
|
|
1454
|
+
),
|
|
1455
|
+
open ? /* @__PURE__ */ jsx13(ChevronUp, { size: 15, className: "flex-shrink-0 text-blue-500", "aria-hidden": true }) : /* @__PURE__ */ jsx13(ChevronDown, { size: 15, className: "flex-shrink-0 text-blue-500", "aria-hidden": true })
|
|
1456
|
+
] })
|
|
1457
|
+
]
|
|
1458
|
+
}
|
|
1459
|
+
),
|
|
1460
|
+
open ? (
|
|
1461
|
+
/**
|
|
1462
|
+
* Teto de altura com rolagem interna, como no financiamento (`max-h-64 overflow-y-auto`).
|
|
1463
|
+
* Sem isso o painel cresce sem limite — e cresce de verdade: o host anexa as chaves de
|
|
1464
|
+
* contexto que ele não conhece ao fim da lista, então uma conversa com bastante estado
|
|
1465
|
+
* empurrava a conversa inteira para fora da tela.
|
|
1466
|
+
*
|
|
1467
|
+
* A diferença: lá o teto envolve o cabeçalho também, então ele rola junto e sai de vista.
|
|
1468
|
+
* Aqui só o corpo rola — o cabeçalho é o que fecha o painel, e perder o clique de fechar
|
|
1469
|
+
* no meio da rolagem é pior do que a economia de uma linha de markup.
|
|
1470
|
+
*/
|
|
1471
|
+
/* @__PURE__ */ jsx13(
|
|
1472
|
+
"div",
|
|
1473
|
+
{
|
|
1474
|
+
className: cn(
|
|
1475
|
+
"cv-scrollbar-thin max-h-64 overflow-y-auto border-t border-blue-200 px-4 pb-4 pt-1 dark:border-blue-900",
|
|
1476
|
+
classNames?.body
|
|
1477
|
+
),
|
|
1478
|
+
children: entries.length === 0 ? /* @__PURE__ */ jsx13("p", { className: "pt-2 text-xs text-gray-500 dark:text-gray-400", children: labels.empty }) : (
|
|
1479
|
+
/**
|
|
1480
|
+
* Três colunas no desktop, como no financiamento — com duas, seis campos viravam uma
|
|
1481
|
+
* coluna alta que empurrava a conversa.
|
|
1482
|
+
*
|
|
1483
|
+
* O que não fizemos igual: lá o painel FILTRA o que não foi coletado, porque a fonte
|
|
1484
|
+
* só guarda o que o cliente respondeu. Aqui a lista de campos é conhecida de antemão,
|
|
1485
|
+
* e "falta endereço" é justamente o que o atendente precisa ver ao assumir. Então o
|
|
1486
|
+
* pendente aparece — tracejado e apagado, para ler como lacuna e não como dado.
|
|
1487
|
+
*/
|
|
1488
|
+
/* @__PURE__ */ jsx13("dl", { className: "mt-2 grid grid-cols-2 gap-2 lg:grid-cols-3", children: entries.map((entry) => {
|
|
1489
|
+
const status = statusOf(entry);
|
|
1490
|
+
return /* @__PURE__ */ jsxs11(
|
|
1491
|
+
"div",
|
|
1492
|
+
{
|
|
1493
|
+
className: cn("min-w-0 rounded-lg p-2.5 transition-all", CARD_STYLE[status]),
|
|
1494
|
+
children: [
|
|
1495
|
+
/* @__PURE__ */ jsxs11("dt", { className: "mb-1 flex items-center justify-center gap-1", children: [
|
|
1496
|
+
entry.icon ? /* @__PURE__ */ jsx13("span", { "aria-hidden": true, className: "text-base leading-none", children: entry.icon }) : null,
|
|
1497
|
+
/* @__PURE__ */ jsx13("span", { className: "truncate text-[10px] font-medium uppercase leading-none tracking-wide text-gray-500 dark:text-gray-400", children: entry.label }),
|
|
1498
|
+
/* @__PURE__ */ jsx13("span", { className: cn("flex-shrink-0 leading-none", GLYPH_STYLE[status]), children: /* @__PURE__ */ jsx13(StatusGlyph, { status }) })
|
|
1499
|
+
] }),
|
|
1500
|
+
/* @__PURE__ */ jsx13(
|
|
1501
|
+
"dd",
|
|
1502
|
+
{
|
|
1503
|
+
className: cn(
|
|
1504
|
+
"truncate text-center text-xs font-semibold",
|
|
1505
|
+
entry.value ? "text-gray-900 dark:text-gray-100" : "italic font-normal text-gray-400 dark:text-gray-500"
|
|
1506
|
+
),
|
|
1507
|
+
title: entry.value ?? labels.notCollected,
|
|
1508
|
+
children: entry.value ?? labels.notCollected
|
|
1509
|
+
}
|
|
1510
|
+
)
|
|
1511
|
+
]
|
|
1512
|
+
},
|
|
1513
|
+
entry.key
|
|
1514
|
+
);
|
|
1515
|
+
}) })
|
|
1516
|
+
)
|
|
1517
|
+
}
|
|
1518
|
+
)
|
|
1519
|
+
) : null
|
|
1520
|
+
]
|
|
1521
|
+
}
|
|
1522
|
+
) })
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1241
1525
|
|
|
1242
|
-
// src/
|
|
1243
|
-
import {
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1526
|
+
// src/WindowExpiredNotice.tsx
|
|
1527
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1528
|
+
var DEFAULT_WINDOW_EXPIRED_LABELS = {
|
|
1529
|
+
title: "\u23F0 Janela de 24h expirada \u2014 o WhatsApp n\xE3o aceita mensagens livres.",
|
|
1530
|
+
description: "Envie uma mensagem de template para reabrir a conversa com o cliente.",
|
|
1531
|
+
sendTemplate: "\u{1F4E8} Enviar Template (HSM)"
|
|
1532
|
+
};
|
|
1533
|
+
function WindowExpiredNotice({
|
|
1534
|
+
onSendTemplate,
|
|
1535
|
+
disabled = false,
|
|
1536
|
+
labels: labelsOverride,
|
|
1537
|
+
className
|
|
1250
1538
|
}) {
|
|
1251
|
-
|
|
1539
|
+
const labels = { ...DEFAULT_WINDOW_EXPIRED_LABELS, ...labelsOverride };
|
|
1540
|
+
return /* @__PURE__ */ jsxs12("div", { role: "status", className: cn("border-t bg-yellow-50 px-4 py-3 text-sm dark:bg-yellow-950", className), children: [
|
|
1541
|
+
/* @__PURE__ */ jsx14("p", { className: "font-medium text-yellow-900 dark:text-yellow-200", children: labels.title }),
|
|
1542
|
+
/* @__PURE__ */ jsx14("p", { className: "text-yellow-800 dark:text-yellow-300", children: labels.description }),
|
|
1543
|
+
onSendTemplate ? /* @__PURE__ */ jsx14("button", { type: "button", onClick: onSendTemplate, disabled, className: "cv-header-action mt-2", children: labels.sendTemplate }) : null
|
|
1544
|
+
] });
|
|
1252
1545
|
}
|
|
1253
|
-
function
|
|
1254
|
-
return
|
|
1546
|
+
function isWindowBlocking(window2) {
|
|
1547
|
+
return window2 === CONVERSATION_WINDOW.EXPIRED;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// src/conversationTranscript.ts
|
|
1551
|
+
var SENDER_LABEL = {
|
|
1552
|
+
customer: "Cliente",
|
|
1553
|
+
bot: "Bot",
|
|
1554
|
+
agent: "Atendente"
|
|
1555
|
+
};
|
|
1556
|
+
function buildTranscriptText(params) {
|
|
1557
|
+
const header = [
|
|
1558
|
+
`Conversa: ${params.clientName ?? params.whatsappNumber}`,
|
|
1559
|
+
`N\xFAmero: ${params.whatsappNumber}`,
|
|
1560
|
+
`Mensagens: ${params.messages.length}`,
|
|
1561
|
+
""
|
|
1562
|
+
];
|
|
1563
|
+
const lines = params.messages.map((message) => {
|
|
1564
|
+
const author = message.agentName ?? SENDER_LABEL[message.sender];
|
|
1565
|
+
return `[${formatStamp(message.timestamp)}] ${author}: ${bodyOf(message)}`;
|
|
1566
|
+
});
|
|
1567
|
+
return [...header, ...lines].join("\n");
|
|
1568
|
+
}
|
|
1569
|
+
function bodyOf(message) {
|
|
1570
|
+
const transcript = message.transcription?.text?.trim();
|
|
1571
|
+
if (message.type === "audio" && transcript) return `[\xE1udio] ${transcript}`;
|
|
1572
|
+
return message.content ?? message.caption ?? `<${message.type}>`;
|
|
1573
|
+
}
|
|
1574
|
+
function formatStamp(timestamp) {
|
|
1575
|
+
if (!timestamp) return "data indispon\xEDvel";
|
|
1576
|
+
const parsed = new Date(timestamp);
|
|
1577
|
+
return Number.isNaN(parsed.getTime()) ? "data indispon\xEDvel" : parsed.toLocaleString("pt-BR");
|
|
1578
|
+
}
|
|
1579
|
+
function buildTranscriptFilename(whatsappNumber, generatedAt) {
|
|
1580
|
+
const stamp = generatedAt.toISOString().slice(0, 10);
|
|
1581
|
+
return `conversa-${whatsappNumber}-${stamp}.txt`;
|
|
1582
|
+
}
|
|
1583
|
+
function downloadTextFile(filename, content) {
|
|
1584
|
+
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
|
1585
|
+
const url = URL.createObjectURL(blob);
|
|
1586
|
+
const anchor = document.createElement("a");
|
|
1587
|
+
anchor.href = url;
|
|
1588
|
+
anchor.download = filename;
|
|
1589
|
+
anchor.click();
|
|
1590
|
+
URL.revokeObjectURL(url);
|
|
1255
1591
|
}
|
|
1256
1592
|
|
|
1257
1593
|
// src/useWaitingNotifications.ts
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1594
|
+
import { useState as useState8, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback4 } from "react";
|
|
1595
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e4;
|
|
1596
|
+
var DEFAULT_LIMIT = 50;
|
|
1597
|
+
var DEFAULT_LABELS = {
|
|
1598
|
+
title: (conversation) => conversation.clientName ?? conversation.whatsappNumber,
|
|
1599
|
+
body: (conversation) => conversation.lastContent ?? "Nova mensagem"
|
|
1600
|
+
};
|
|
1601
|
+
function useWaitingNotifications(params) {
|
|
1602
|
+
const conversationsContext = useConversations();
|
|
1603
|
+
const [conversations, setConversations] = useState8([]);
|
|
1604
|
+
const previousUnreadMap = useRef4(/* @__PURE__ */ new Map());
|
|
1605
|
+
const notifiedIds = useRef4(/* @__PURE__ */ new Set());
|
|
1606
|
+
const isPollingRef = useRef4(false);
|
|
1607
|
+
const isEnabled = params?.enabled ?? true;
|
|
1608
|
+
const intervalMs = params?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1609
|
+
const icon = params?.icon;
|
|
1610
|
+
const listParams = params?.params;
|
|
1611
|
+
const labelTitle = params?.labels?.title ?? DEFAULT_LABELS.title;
|
|
1612
|
+
const labelBody = params?.labels?.body ?? DEFAULT_LABELS.body;
|
|
1613
|
+
const refresh = useCallback4(async () => {
|
|
1614
|
+
if (!conversationsContext || isPollingRef.current) return;
|
|
1615
|
+
isPollingRef.current = true;
|
|
1265
1616
|
try {
|
|
1266
|
-
const result =
|
|
1617
|
+
const result = conversationsOf(
|
|
1618
|
+
await conversationsContext.api.fetchConversations({ limit: DEFAULT_LIMIT, ...listParams })
|
|
1619
|
+
);
|
|
1267
1620
|
setConversations(result);
|
|
1268
1621
|
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
|
-
}
|
|
1622
|
+
for (const conversation of result) currentMap.set(conversation.id, conversation.unread);
|
|
1623
|
+
for (const conversation of result) {
|
|
1624
|
+
if (conversation.unread <= 0) continue;
|
|
1625
|
+
const previousUnread = previousUnreadMap.current.get(conversation.id) ?? 0;
|
|
1626
|
+
if (conversation.unread <= previousUnread || notifiedIds.current.has(conversation.id)) continue;
|
|
1627
|
+
notifiedIds.current.add(conversation.id);
|
|
1628
|
+
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "granted") {
|
|
1629
|
+
new Notification(labelTitle(conversation), { body: labelBody(conversation), ...icon ? { icon } : {} });
|
|
1282
1630
|
}
|
|
1283
1631
|
}
|
|
1284
1632
|
previousUnreadMap.current = currentMap;
|
|
1285
1633
|
} catch {
|
|
1634
|
+
} finally {
|
|
1635
|
+
isPollingRef.current = false;
|
|
1286
1636
|
}
|
|
1287
|
-
}, [
|
|
1288
|
-
|
|
1637
|
+
}, [conversationsContext, listParams, icon, labelTitle, labelBody]);
|
|
1638
|
+
useEffect5(() => {
|
|
1639
|
+
if (!isEnabled) return;
|
|
1289
1640
|
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "default") {
|
|
1290
1641
|
Notification.requestPermission();
|
|
1291
1642
|
}
|
|
1292
|
-
|
|
1293
|
-
const interval = setInterval(
|
|
1643
|
+
void refresh();
|
|
1644
|
+
const interval = setInterval(() => void refresh(), intervalMs);
|
|
1294
1645
|
return () => clearInterval(interval);
|
|
1295
|
-
}, [
|
|
1296
|
-
const unreadCount = conversations.reduce((sum,
|
|
1297
|
-
return { unreadCount, conversations };
|
|
1646
|
+
}, [refresh, isEnabled, intervalMs]);
|
|
1647
|
+
const unreadCount = conversations.reduce((sum, conversation) => sum + conversation.unread, 0);
|
|
1648
|
+
return { unreadCount, conversations, refresh };
|
|
1298
1649
|
}
|
|
1299
1650
|
|
|
1300
1651
|
// src/settings/WhatsAppTemplateSettingsForm.tsx
|
|
1301
1652
|
import { Plus, RefreshCw, Save, Trash2 } from "lucide-react";
|
|
1302
|
-
import { jsx as
|
|
1303
|
-
var
|
|
1653
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1654
|
+
var DEFAULT_LABELS2 = {
|
|
1304
1655
|
sectionTitle: "WhatsApp",
|
|
1305
1656
|
sectionDescription: "Template usado para reabrir a janela de 24h com o cliente.",
|
|
1306
1657
|
templateLabel: "Template aprovado",
|
|
@@ -1334,7 +1685,7 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1334
1685
|
onSave,
|
|
1335
1686
|
labels: labelsOverride
|
|
1336
1687
|
}) {
|
|
1337
|
-
const labels = { ...
|
|
1688
|
+
const labels = { ...DEFAULT_LABELS2, ...labelsOverride };
|
|
1338
1689
|
const selected = templates.find((template) => template.name === selectedTemplateName);
|
|
1339
1690
|
function handleSelect(name) {
|
|
1340
1691
|
const template = templates.find((t) => t.name === name);
|
|
@@ -1358,16 +1709,16 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1358
1709
|
}
|
|
1359
1710
|
onVariablesChange([...variables, token]);
|
|
1360
1711
|
}
|
|
1361
|
-
return /* @__PURE__ */
|
|
1362
|
-
/* @__PURE__ */
|
|
1363
|
-
/* @__PURE__ */
|
|
1364
|
-
/* @__PURE__ */
|
|
1712
|
+
return /* @__PURE__ */ jsxs13("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1713
|
+
/* @__PURE__ */ jsxs13("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1714
|
+
/* @__PURE__ */ jsx15("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }),
|
|
1715
|
+
/* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1365
1716
|
] }),
|
|
1366
|
-
/* @__PURE__ */
|
|
1367
|
-
/* @__PURE__ */
|
|
1368
|
-
/* @__PURE__ */
|
|
1369
|
-
/* @__PURE__ */
|
|
1370
|
-
/* @__PURE__ */
|
|
1717
|
+
/* @__PURE__ */ jsxs13("form", { onSubmit: onSave, className: "p-6 space-y-4", children: [
|
|
1718
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1719
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between mb-1.5", children: [
|
|
1720
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.templateLabel }),
|
|
1721
|
+
/* @__PURE__ */ jsxs13(
|
|
1371
1722
|
"button",
|
|
1372
1723
|
{
|
|
1373
1724
|
type: "button",
|
|
@@ -1375,31 +1726,31 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1375
1726
|
disabled: loadingTemplates,
|
|
1376
1727
|
className: "flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-50",
|
|
1377
1728
|
children: [
|
|
1378
|
-
/* @__PURE__ */
|
|
1729
|
+
/* @__PURE__ */ jsx15(RefreshCw, { size: 11, className: loadingTemplates ? "animate-spin" : "" }),
|
|
1379
1730
|
labels.refreshList
|
|
1380
1731
|
]
|
|
1381
1732
|
}
|
|
1382
1733
|
)
|
|
1383
1734
|
] }),
|
|
1384
|
-
templatesError && /* @__PURE__ */
|
|
1735
|
+
templatesError && /* @__PURE__ */ jsxs13("div", { className: "mb-2 rounded-lg bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800 px-3 py-2 text-xs text-amber-700 dark:text-amber-400", children: [
|
|
1385
1736
|
labels.errorLoading,
|
|
1386
|
-
/* @__PURE__ */
|
|
1387
|
-
/* @__PURE__ */
|
|
1737
|
+
/* @__PURE__ */ jsx15("br", {}),
|
|
1738
|
+
/* @__PURE__ */ jsxs13("span", { className: "text-gray-500 dark:text-gray-400 mt-0.5 block", children: [
|
|
1388
1739
|
labels.currentNameSaved,
|
|
1389
1740
|
" ",
|
|
1390
|
-
/* @__PURE__ */
|
|
1741
|
+
/* @__PURE__ */ jsx15("strong", { children: selectedTemplateName || "\u2014" })
|
|
1391
1742
|
] })
|
|
1392
1743
|
] }),
|
|
1393
|
-
loadingTemplates && /* @__PURE__ */
|
|
1394
|
-
!loadingTemplates && !templatesError && /* @__PURE__ */
|
|
1744
|
+
loadingTemplates && /* @__PURE__ */ jsx15("div", { className: "h-10 rounded-xl bg-gray-100 dark:bg-gray-700 animate-pulse" }),
|
|
1745
|
+
!loadingTemplates && !templatesError && /* @__PURE__ */ jsxs13(
|
|
1395
1746
|
"select",
|
|
1396
1747
|
{
|
|
1397
1748
|
value: selectedTemplateName,
|
|
1398
1749
|
onChange: (e) => handleSelect(e.target.value),
|
|
1399
1750
|
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
1751
|
children: [
|
|
1401
|
-
/* @__PURE__ */
|
|
1402
|
-
templates.map((template) => /* @__PURE__ */
|
|
1752
|
+
/* @__PURE__ */ jsx15("option", { value: "", children: labels.selectPlaceholder }),
|
|
1753
|
+
templates.map((template) => /* @__PURE__ */ jsxs13("option", { value: template.name, disabled: template.status !== "APPROVED", children: [
|
|
1403
1754
|
template.displayName,
|
|
1404
1755
|
" \xB7 ",
|
|
1405
1756
|
template.shortId,
|
|
@@ -1411,27 +1762,27 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1411
1762
|
]
|
|
1412
1763
|
}
|
|
1413
1764
|
),
|
|
1414
|
-
selected?.bodyText && /* @__PURE__ */
|
|
1765
|
+
selected?.bodyText && /* @__PURE__ */ jsx15("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
1766
|
] }),
|
|
1416
|
-
/* @__PURE__ */
|
|
1417
|
-
/* @__PURE__ */
|
|
1418
|
-
/* @__PURE__ */
|
|
1419
|
-
/* @__PURE__ */
|
|
1767
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
1768
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between mb-1.5", children: [
|
|
1769
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.variablesLabel }),
|
|
1770
|
+
/* @__PURE__ */ jsxs13(
|
|
1420
1771
|
"button",
|
|
1421
1772
|
{
|
|
1422
1773
|
type: "button",
|
|
1423
1774
|
onClick: () => onVariablesChange([...variables, ""]),
|
|
1424
1775
|
className: "flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline",
|
|
1425
1776
|
children: [
|
|
1426
|
-
/* @__PURE__ */
|
|
1777
|
+
/* @__PURE__ */ jsx15(Plus, { size: 12 }),
|
|
1427
1778
|
labels.addVariable
|
|
1428
1779
|
]
|
|
1429
1780
|
}
|
|
1430
1781
|
)
|
|
1431
1782
|
] }),
|
|
1432
|
-
availableVariables.length > 0 && /* @__PURE__ */
|
|
1433
|
-
/* @__PURE__ */
|
|
1434
|
-
/* @__PURE__ */
|
|
1783
|
+
availableVariables.length > 0 && /* @__PURE__ */ jsxs13("div", { className: "mb-3 rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700/40 p-2.5", children: [
|
|
1784
|
+
/* @__PURE__ */ jsx15("p", { className: "text-xs font-medium text-gray-600 dark:text-gray-300 mb-1.5", children: labels.variablesAvailableTitle }),
|
|
1785
|
+
/* @__PURE__ */ jsx15("div", { className: "flex flex-wrap gap-1.5", children: availableVariables.map((available) => /* @__PURE__ */ jsxs13(
|
|
1435
1786
|
"button",
|
|
1436
1787
|
{
|
|
1437
1788
|
type: "button",
|
|
@@ -1439,8 +1790,8 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1439
1790
|
title: `Inserir ${available.token}`,
|
|
1440
1791
|
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
1792
|
children: [
|
|
1442
|
-
/* @__PURE__ */
|
|
1443
|
-
/* @__PURE__ */
|
|
1793
|
+
/* @__PURE__ */ jsx15("code", { className: "text-blue-600 dark:text-blue-300", children: available.token }),
|
|
1794
|
+
/* @__PURE__ */ jsxs13("span", { className: "text-gray-400 dark:text-gray-500", children: [
|
|
1444
1795
|
"\xB7 ",
|
|
1445
1796
|
available.label
|
|
1446
1797
|
] })
|
|
@@ -1448,13 +1799,13 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1448
1799
|
},
|
|
1449
1800
|
available.token
|
|
1450
1801
|
)) }),
|
|
1451
|
-
/* @__PURE__ */
|
|
1802
|
+
/* @__PURE__ */ jsx15("p", { className: "mt-1.5 text-[11px] text-gray-400 dark:text-gray-500", children: labels.variablesAvailableHint })
|
|
1452
1803
|
] }),
|
|
1453
|
-
/* @__PURE__ */
|
|
1454
|
-
variables.length === 0 && /* @__PURE__ */
|
|
1455
|
-
/* @__PURE__ */
|
|
1456
|
-
/* @__PURE__ */
|
|
1457
|
-
/* @__PURE__ */
|
|
1804
|
+
/* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-400 dark:text-gray-500 mb-2", children: labels.variablesMappingHint }),
|
|
1805
|
+
variables.length === 0 && /* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-400 dark:text-gray-500 italic", children: labels.noVariables }),
|
|
1806
|
+
/* @__PURE__ */ jsx15("div", { className: "space-y-2", children: variables.map((variable, index) => /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", children: [
|
|
1807
|
+
/* @__PURE__ */ jsx15("span", { className: "text-xs text-gray-400 dark:text-gray-500 w-6 text-right flex-shrink-0", children: `{{${index + 1}}}` }),
|
|
1808
|
+
/* @__PURE__ */ jsx15(
|
|
1458
1809
|
"input",
|
|
1459
1810
|
{
|
|
1460
1811
|
type: "text",
|
|
@@ -1464,27 +1815,27 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1464
1815
|
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
1816
|
}
|
|
1466
1817
|
),
|
|
1467
|
-
/* @__PURE__ */
|
|
1818
|
+
/* @__PURE__ */ jsx15(
|
|
1468
1819
|
"button",
|
|
1469
1820
|
{
|
|
1470
1821
|
type: "button",
|
|
1471
1822
|
onClick: () => removeVariable(index),
|
|
1472
1823
|
className: "text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors flex-shrink-0",
|
|
1473
1824
|
title: labels.removeVariable,
|
|
1474
|
-
children: /* @__PURE__ */
|
|
1825
|
+
children: /* @__PURE__ */ jsx15(Trash2, { size: 14 })
|
|
1475
1826
|
}
|
|
1476
1827
|
)
|
|
1477
1828
|
] }, index)) })
|
|
1478
1829
|
] }),
|
|
1479
|
-
saveSuccess && /* @__PURE__ */
|
|
1480
|
-
/* @__PURE__ */
|
|
1830
|
+
saveSuccess && /* @__PURE__ */ jsx15("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 }),
|
|
1831
|
+
/* @__PURE__ */ jsx15("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs13(
|
|
1481
1832
|
"button",
|
|
1482
1833
|
{
|
|
1483
1834
|
type: "submit",
|
|
1484
1835
|
disabled: saving || !selectedTemplateName.trim(),
|
|
1485
1836
|
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
1837
|
children: [
|
|
1487
|
-
saving ? /* @__PURE__ */
|
|
1838
|
+
saving ? /* @__PURE__ */ jsx15("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx15(Save, { size: 14 }),
|
|
1488
1839
|
saving ? labels.saving : labels.save
|
|
1489
1840
|
]
|
|
1490
1841
|
}
|
|
@@ -1494,13 +1845,14 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1494
1845
|
}
|
|
1495
1846
|
|
|
1496
1847
|
// src/settings/WhatsAppCreateTemplateForm.tsx
|
|
1497
|
-
import { SendHorizonal } from "lucide-react";
|
|
1498
|
-
import { jsx as
|
|
1499
|
-
var
|
|
1848
|
+
import { SendHorizonal as SendHorizonal2 } from "lucide-react";
|
|
1849
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1850
|
+
var DEFAULT_LABELS3 = {
|
|
1500
1851
|
sectionTitle: "Criar novo template",
|
|
1501
1852
|
sectionDescription: "Envia um template para aprova\xE7\xE3o da Meta.",
|
|
1502
1853
|
nameLabel: "Nome do template",
|
|
1503
1854
|
nameHint: "Somente letras min\xFAsculas, n\xFAmeros e underscore.",
|
|
1855
|
+
namePlaceholder: "reengajamento_cliente",
|
|
1504
1856
|
categoryLabel: "Categoria",
|
|
1505
1857
|
languageLabel: "Idioma",
|
|
1506
1858
|
headerLabel: "Cabe\xE7alho",
|
|
@@ -1546,82 +1898,82 @@ function WhatsAppCreateTemplateForm({
|
|
|
1546
1898
|
labels: labelsOverride,
|
|
1547
1899
|
variableExamples = DEFAULT_VARIABLE_EXAMPLES
|
|
1548
1900
|
}) {
|
|
1549
|
-
const labels = { ...
|
|
1901
|
+
const labels = { ...DEFAULT_LABELS3, ...labelsOverride };
|
|
1550
1902
|
const detectedVariables = detectVariables(value.bodyText);
|
|
1551
1903
|
function set(key, next) {
|
|
1552
1904
|
onChange({ ...value, [key]: next });
|
|
1553
1905
|
}
|
|
1554
|
-
return /* @__PURE__ */
|
|
1555
|
-
/* @__PURE__ */
|
|
1556
|
-
/* @__PURE__ */
|
|
1557
|
-
/* @__PURE__ */
|
|
1906
|
+
return /* @__PURE__ */ jsxs14("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1907
|
+
/* @__PURE__ */ jsxs14("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1908
|
+
/* @__PURE__ */ jsx16("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }),
|
|
1909
|
+
/* @__PURE__ */ jsx16("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1558
1910
|
] }),
|
|
1559
|
-
/* @__PURE__ */
|
|
1560
|
-
/* @__PURE__ */
|
|
1561
|
-
/* @__PURE__ */
|
|
1562
|
-
/* @__PURE__ */
|
|
1563
|
-
/* @__PURE__ */
|
|
1911
|
+
/* @__PURE__ */ jsxs14("form", { onSubmit, className: "p-6 space-y-4", children: [
|
|
1912
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1913
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.nameLabel }),
|
|
1914
|
+
/* @__PURE__ */ jsx16("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.nameHint }),
|
|
1915
|
+
/* @__PURE__ */ jsx16(
|
|
1564
1916
|
"input",
|
|
1565
1917
|
{
|
|
1566
1918
|
type: "text",
|
|
1567
1919
|
value: value.name,
|
|
1568
1920
|
onChange: (e) => set("name", e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, "_")),
|
|
1569
|
-
placeholder:
|
|
1921
|
+
placeholder: labels.namePlaceholder,
|
|
1570
1922
|
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
1923
|
required: true
|
|
1572
1924
|
}
|
|
1573
1925
|
)
|
|
1574
1926
|
] }),
|
|
1575
|
-
/* @__PURE__ */
|
|
1576
|
-
/* @__PURE__ */
|
|
1577
|
-
/* @__PURE__ */
|
|
1578
|
-
/* @__PURE__ */
|
|
1927
|
+
/* @__PURE__ */ jsxs14("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
1928
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1929
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.categoryLabel }),
|
|
1930
|
+
/* @__PURE__ */ jsxs14(
|
|
1579
1931
|
"select",
|
|
1580
1932
|
{
|
|
1581
1933
|
value: value.category,
|
|
1582
1934
|
onChange: (e) => set("category", e.target.value),
|
|
1583
1935
|
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
1936
|
children: [
|
|
1585
|
-
/* @__PURE__ */
|
|
1586
|
-
/* @__PURE__ */
|
|
1937
|
+
/* @__PURE__ */ jsx16("option", { value: "UTILITY", children: "UTILITY (Transacional)" }),
|
|
1938
|
+
/* @__PURE__ */ jsx16("option", { value: "MARKETING", children: "MARKETING (Promocional)" })
|
|
1587
1939
|
]
|
|
1588
1940
|
}
|
|
1589
1941
|
)
|
|
1590
1942
|
] }),
|
|
1591
|
-
/* @__PURE__ */
|
|
1592
|
-
/* @__PURE__ */
|
|
1593
|
-
/* @__PURE__ */
|
|
1943
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1944
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.languageLabel }),
|
|
1945
|
+
/* @__PURE__ */ jsxs14(
|
|
1594
1946
|
"select",
|
|
1595
1947
|
{
|
|
1596
1948
|
value: value.language,
|
|
1597
1949
|
onChange: (e) => set("language", e.target.value),
|
|
1598
1950
|
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
1951
|
children: [
|
|
1600
|
-
/* @__PURE__ */
|
|
1601
|
-
/* @__PURE__ */
|
|
1602
|
-
/* @__PURE__ */
|
|
1952
|
+
/* @__PURE__ */ jsx16("option", { value: "pt_BR", children: "Portugu\xEAs (Brasil)" }),
|
|
1953
|
+
/* @__PURE__ */ jsx16("option", { value: "en_US", children: "Ingl\xEAs (EUA)" }),
|
|
1954
|
+
/* @__PURE__ */ jsx16("option", { value: "es_ES", children: "Espanhol" })
|
|
1603
1955
|
]
|
|
1604
1956
|
}
|
|
1605
1957
|
)
|
|
1606
1958
|
] })
|
|
1607
1959
|
] }),
|
|
1608
|
-
/* @__PURE__ */
|
|
1609
|
-
/* @__PURE__ */
|
|
1610
|
-
/* @__PURE__ */
|
|
1611
|
-
/* @__PURE__ */
|
|
1612
|
-
/* @__PURE__ */
|
|
1960
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1961
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.headerLabel }),
|
|
1962
|
+
/* @__PURE__ */ jsx16("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.headerHint }),
|
|
1963
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex gap-2", children: [
|
|
1964
|
+
/* @__PURE__ */ jsxs14(
|
|
1613
1965
|
"select",
|
|
1614
1966
|
{
|
|
1615
1967
|
value: value.headerType,
|
|
1616
1968
|
onChange: (e) => set("headerType", e.target.value),
|
|
1617
1969
|
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
1970
|
children: [
|
|
1619
|
-
/* @__PURE__ */
|
|
1620
|
-
/* @__PURE__ */
|
|
1971
|
+
/* @__PURE__ */ jsx16("option", { value: "NONE", children: labels.headerNone }),
|
|
1972
|
+
/* @__PURE__ */ jsx16("option", { value: "TEXT", children: labels.headerText })
|
|
1621
1973
|
]
|
|
1622
1974
|
}
|
|
1623
1975
|
),
|
|
1624
|
-
value.headerType === "TEXT" && /* @__PURE__ */
|
|
1976
|
+
value.headerType === "TEXT" && /* @__PURE__ */ jsx16(
|
|
1625
1977
|
"input",
|
|
1626
1978
|
{
|
|
1627
1979
|
type: "text",
|
|
@@ -1634,10 +1986,10 @@ function WhatsAppCreateTemplateForm({
|
|
|
1634
1986
|
)
|
|
1635
1987
|
] })
|
|
1636
1988
|
] }),
|
|
1637
|
-
/* @__PURE__ */
|
|
1638
|
-
/* @__PURE__ */
|
|
1639
|
-
/* @__PURE__ */
|
|
1640
|
-
/* @__PURE__ */
|
|
1989
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1990
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.bodyLabel }),
|
|
1991
|
+
/* @__PURE__ */ jsx16("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.bodyHint }),
|
|
1992
|
+
/* @__PURE__ */ jsx16(
|
|
1641
1993
|
"textarea",
|
|
1642
1994
|
{
|
|
1643
1995
|
value: value.bodyText,
|
|
@@ -1648,26 +2000,26 @@ function WhatsAppCreateTemplateForm({
|
|
|
1648
2000
|
required: true
|
|
1649
2001
|
}
|
|
1650
2002
|
),
|
|
1651
|
-
detectedVariables.length > 0 && /* @__PURE__ */
|
|
1652
|
-
/* @__PURE__ */
|
|
1653
|
-
/* @__PURE__ */
|
|
1654
|
-
/* @__PURE__ */
|
|
1655
|
-
/* @__PURE__ */
|
|
2003
|
+
detectedVariables.length > 0 && /* @__PURE__ */ jsxs14("div", { className: "mt-2 rounded-lg bg-gray-50 dark:bg-gray-700/50 border border-gray-200 dark:border-gray-600 px-3 py-2", children: [
|
|
2004
|
+
/* @__PURE__ */ jsx16("p", { className: "font-medium text-gray-500 dark:text-gray-400 mb-1.5 uppercase tracking-wide text-xs", children: labels.detectedVariables }),
|
|
2005
|
+
/* @__PURE__ */ jsx16("div", { className: "flex flex-wrap gap-2", children: detectedVariables.map((num) => /* @__PURE__ */ jsxs14("span", { className: "inline-flex items-center gap-1 text-xs", children: [
|
|
2006
|
+
/* @__PURE__ */ jsx16("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}}}` }),
|
|
2007
|
+
/* @__PURE__ */ jsxs14("span", { className: "text-gray-400 dark:text-gray-500", children: [
|
|
1656
2008
|
"= ",
|
|
1657
2009
|
variableExamples[Number(num) - 1] ?? labels.variableLabel(Number(num))
|
|
1658
2010
|
] })
|
|
1659
2011
|
] }, num)) }),
|
|
1660
|
-
/* @__PURE__ */
|
|
2012
|
+
/* @__PURE__ */ jsxs14("p", { className: "text-gray-400 dark:text-gray-500 mt-1.5 text-xs", children: [
|
|
1661
2013
|
labels.configureHintPrefix,
|
|
1662
|
-
/* @__PURE__ */
|
|
2014
|
+
/* @__PURE__ */ jsx16("strong", { children: labels.configureHintLink }),
|
|
1663
2015
|
labels.configureHintSuffix
|
|
1664
2016
|
] })
|
|
1665
2017
|
] })
|
|
1666
2018
|
] }),
|
|
1667
|
-
/* @__PURE__ */
|
|
1668
|
-
/* @__PURE__ */
|
|
1669
|
-
/* @__PURE__ */
|
|
1670
|
-
/* @__PURE__ */
|
|
2019
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
2020
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.footerLabel }),
|
|
2021
|
+
/* @__PURE__ */ jsx16("p", { className: "text-xs text-gray-400 mb-1.5", children: labels.footerHint }),
|
|
2022
|
+
/* @__PURE__ */ jsx16(
|
|
1671
2023
|
"input",
|
|
1672
2024
|
{
|
|
1673
2025
|
type: "text",
|
|
@@ -1679,48 +2031,48 @@ function WhatsAppCreateTemplateForm({
|
|
|
1679
2031
|
}
|
|
1680
2032
|
)
|
|
1681
2033
|
] }),
|
|
1682
|
-
/* @__PURE__ */
|
|
1683
|
-
/* @__PURE__ */
|
|
1684
|
-
/* @__PURE__ */
|
|
1685
|
-
/* @__PURE__ */
|
|
1686
|
-
/* @__PURE__ */
|
|
1687
|
-
/* @__PURE__ */
|
|
1688
|
-
/* @__PURE__ */
|
|
1689
|
-
/* @__PURE__ */
|
|
2034
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
2035
|
+
/* @__PURE__ */ jsx16("label", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block", children: labels.previewLabel }),
|
|
2036
|
+
/* @__PURE__ */ jsxs14("div", { className: "rounded-2xl bg-[#e5ddd5] dark:bg-[#1a1a2e] border border-gray-300 dark:border-gray-600 overflow-hidden shadow-inner", children: [
|
|
2037
|
+
/* @__PURE__ */ jsxs14("div", { className: "bg-[#075e54] px-4 py-2.5 flex items-center gap-2", children: [
|
|
2038
|
+
/* @__PURE__ */ jsx16("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() }),
|
|
2039
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
2040
|
+
/* @__PURE__ */ jsx16("p", { className: "text-white text-xs font-semibold leading-tight", children: previewCompanyName }),
|
|
2041
|
+
/* @__PURE__ */ jsx16("p", { className: "text-white/60 text-xs", children: labels.previewOnline })
|
|
1690
2042
|
] })
|
|
1691
2043
|
] }),
|
|
1692
|
-
/* @__PURE__ */
|
|
1693
|
-
/* @__PURE__ */
|
|
1694
|
-
/* @__PURE__ */
|
|
1695
|
-
/* @__PURE__ */
|
|
2044
|
+
/* @__PURE__ */ jsxs14("div", { className: "px-3 py-4 space-y-3", style: { minHeight: "120px" }, children: [
|
|
2045
|
+
/* @__PURE__ */ jsx16("div", { className: "flex justify-start", style: { maxWidth: "85%" }, children: /* @__PURE__ */ jsxs14("div", { className: "bg-white dark:bg-gray-700 rounded-lg rounded-tl-none px-3 py-2 shadow-sm", children: [
|
|
2046
|
+
/* @__PURE__ */ jsx16("p", { className: "text-gray-500 dark:text-gray-400 leading-tight text-sm", children: labels.previewDescription }),
|
|
2047
|
+
/* @__PURE__ */ jsx16("p", { className: "text-gray-400 dark:text-gray-500 mt-1 text-right text-[9px]", children: "12:00" })
|
|
1696
2048
|
] }) }),
|
|
1697
|
-
/* @__PURE__ */
|
|
1698
|
-
value.headerType === "TEXT" && value.headerText && /* @__PURE__ */
|
|
1699
|
-
/* @__PURE__ */
|
|
1700
|
-
value.footerText && /* @__PURE__ */
|
|
1701
|
-
/* @__PURE__ */
|
|
2049
|
+
/* @__PURE__ */ jsx16("div", { className: "flex justify-end ml-auto", style: { maxWidth: "85%" }, children: /* @__PURE__ */ jsxs14("div", { className: "bg-[#dcf8c6] dark:bg-[#1b5e20] rounded-lg rounded-tr-none px-3 py-2 shadow-sm", children: [
|
|
2050
|
+
value.headerType === "TEXT" && value.headerText && /* @__PURE__ */ jsx16("p", { className: "text-xs font-bold text-gray-700 dark:text-gray-200 mb-1", children: fillPreviewTokens(value.headerText) || labels.previewHeaderFallback }),
|
|
2051
|
+
/* @__PURE__ */ jsx16("p", { className: "text-sm text-gray-800 dark:text-gray-100 leading-relaxed", children: value.bodyText ? fillPreviewTokens(value.bodyText) : /* @__PURE__ */ jsx16("span", { className: "text-gray-400 italic", children: labels.previewBodyPlaceholder }) }),
|
|
2052
|
+
value.footerText && /* @__PURE__ */ jsx16("p", { className: "text-gray-400 dark:text-gray-400 mt-1 text-xs", children: value.footerText }),
|
|
2053
|
+
/* @__PURE__ */ jsx16("p", { className: "text-gray-400 dark:text-gray-400 mt-1 text-right text-[9px]", children: "12:01 \u2713" })
|
|
1702
2054
|
] }) })
|
|
1703
2055
|
] })
|
|
1704
2056
|
] })
|
|
1705
2057
|
] }),
|
|
1706
|
-
result && /* @__PURE__ */
|
|
2058
|
+
result && /* @__PURE__ */ jsxs14(
|
|
1707
2059
|
"div",
|
|
1708
2060
|
{
|
|
1709
2061
|
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
2062
|
children: [
|
|
1711
2063
|
result.message,
|
|
1712
|
-
result.ok && result.status && /* @__PURE__ */
|
|
2064
|
+
result.ok && result.status && /* @__PURE__ */ jsx16("p", { className: "text-xs mt-1 opacity-75", children: labels.successStatus(result.status) })
|
|
1713
2065
|
]
|
|
1714
2066
|
}
|
|
1715
2067
|
),
|
|
1716
|
-
/* @__PURE__ */
|
|
2068
|
+
/* @__PURE__ */ jsx16("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs14(
|
|
1717
2069
|
"button",
|
|
1718
2070
|
{
|
|
1719
2071
|
type: "submit",
|
|
1720
2072
|
disabled: submitting || !value.name || !value.bodyText,
|
|
1721
2073
|
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
2074
|
children: [
|
|
1723
|
-
submitting ? /* @__PURE__ */
|
|
2075
|
+
submitting ? /* @__PURE__ */ jsx16("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx16(SendHorizonal2, { size: 14 }),
|
|
1724
2076
|
submitting ? labels.sending : labels.sendForApproval
|
|
1725
2077
|
]
|
|
1726
2078
|
}
|
|
@@ -1731,8 +2083,8 @@ function WhatsAppCreateTemplateForm({
|
|
|
1731
2083
|
|
|
1732
2084
|
// src/settings/WelcomeFarewellForm.tsx
|
|
1733
2085
|
import { LogOut, Mail, Save as Save2 } from "lucide-react";
|
|
1734
|
-
import { jsx as
|
|
1735
|
-
var
|
|
2086
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2087
|
+
var DEFAULT_LABELS4 = {
|
|
1736
2088
|
sectionTitle: "Boas-vindas e encerramento",
|
|
1737
2089
|
welcomeMessage: "Mensagem de boas-vindas",
|
|
1738
2090
|
welcomeMessagePlaceholder: "Ol\xE1! Sou o assistente virtual da {empresa}...",
|
|
@@ -1758,16 +2110,16 @@ function WelcomeFarewellForm({
|
|
|
1758
2110
|
farewellPlaceholders = DEFAULT_FAREWELL_PLACEHOLDERS,
|
|
1759
2111
|
labels: labelsOverride
|
|
1760
2112
|
}) {
|
|
1761
|
-
const labels = { ...
|
|
1762
|
-
return /* @__PURE__ */
|
|
1763
|
-
/* @__PURE__ */
|
|
1764
|
-
/* @__PURE__ */
|
|
1765
|
-
/* @__PURE__ */
|
|
1766
|
-
/* @__PURE__ */
|
|
1767
|
-
/* @__PURE__ */
|
|
2113
|
+
const labels = { ...DEFAULT_LABELS4, ...labelsOverride };
|
|
2114
|
+
return /* @__PURE__ */ jsxs15("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
2115
|
+
/* @__PURE__ */ jsx17("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: /* @__PURE__ */ jsx17("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }) }),
|
|
2116
|
+
/* @__PURE__ */ jsxs15("form", { onSubmit: onSave, className: "p-6 space-y-5", children: [
|
|
2117
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
2118
|
+
/* @__PURE__ */ jsxs15("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5 flex items-center gap-1.5", children: [
|
|
2119
|
+
/* @__PURE__ */ jsx17(Mail, { size: 13, className: "text-blue-600 dark:text-blue-400" }),
|
|
1768
2120
|
labels.welcomeMessage
|
|
1769
2121
|
] }),
|
|
1770
|
-
/* @__PURE__ */
|
|
2122
|
+
/* @__PURE__ */ jsx17(
|
|
1771
2123
|
WhatsAppMessageEditor,
|
|
1772
2124
|
{
|
|
1773
2125
|
value: welcomeMessage,
|
|
@@ -1776,14 +2128,14 @@ function WelcomeFarewellForm({
|
|
|
1776
2128
|
placeholders: welcomePlaceholders
|
|
1777
2129
|
}
|
|
1778
2130
|
),
|
|
1779
|
-
/* @__PURE__ */
|
|
2131
|
+
/* @__PURE__ */ jsx17("p", { className: "mt-1.5 text-xs text-gray-500 dark:text-gray-400", children: labels.welcomeMessageHint })
|
|
1780
2132
|
] }),
|
|
1781
|
-
/* @__PURE__ */
|
|
1782
|
-
/* @__PURE__ */
|
|
1783
|
-
/* @__PURE__ */
|
|
2133
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
2134
|
+
/* @__PURE__ */ jsxs15("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5 flex items-center gap-1.5", children: [
|
|
2135
|
+
/* @__PURE__ */ jsx17(LogOut, { size: 13, className: "text-blue-600 dark:text-blue-400" }),
|
|
1784
2136
|
labels.farewellMessage
|
|
1785
2137
|
] }),
|
|
1786
|
-
/* @__PURE__ */
|
|
2138
|
+
/* @__PURE__ */ jsx17(
|
|
1787
2139
|
WhatsAppMessageEditor,
|
|
1788
2140
|
{
|
|
1789
2141
|
value: farewellMessage,
|
|
@@ -1792,17 +2144,109 @@ function WelcomeFarewellForm({
|
|
|
1792
2144
|
placeholders: farewellPlaceholders
|
|
1793
2145
|
}
|
|
1794
2146
|
),
|
|
1795
|
-
/* @__PURE__ */
|
|
2147
|
+
/* @__PURE__ */ jsx17("p", { className: "mt-1.5 text-xs text-gray-500 dark:text-gray-400", children: labels.farewellMessageHint })
|
|
1796
2148
|
] }),
|
|
1797
|
-
saveSuccess && /* @__PURE__ */
|
|
1798
|
-
/* @__PURE__ */
|
|
2149
|
+
saveSuccess && /* @__PURE__ */ jsx17("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 }),
|
|
2150
|
+
/* @__PURE__ */ jsx17("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs15(
|
|
1799
2151
|
"button",
|
|
1800
2152
|
{
|
|
1801
2153
|
type: "submit",
|
|
1802
2154
|
disabled: saving,
|
|
1803
2155
|
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
2156
|
children: [
|
|
1805
|
-
saving ? /* @__PURE__ */
|
|
2157
|
+
saving ? /* @__PURE__ */ jsx17("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx17(Save2, { size: 14 }),
|
|
2158
|
+
saving ? labels.saving : labels.save
|
|
2159
|
+
]
|
|
2160
|
+
}
|
|
2161
|
+
) })
|
|
2162
|
+
] })
|
|
2163
|
+
] });
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// src/settings/TranscriptionSettingsForm.tsx
|
|
2167
|
+
import { AudioLines, Save as Save3 } from "lucide-react";
|
|
2168
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2169
|
+
var DEFAULT_LABELS5 = {
|
|
2170
|
+
sectionTitle: "Transcri\xE7\xE3o de \xE1udio",
|
|
2171
|
+
enabled: "Transcrever notas de voz",
|
|
2172
|
+
enabledHint: "Converte o \xE1udio do cliente em texto, que o atendente l\xEA e copia direto da conversa.",
|
|
2173
|
+
modeTitle: "Quando transcrever",
|
|
2174
|
+
modeAuto: "Automaticamente",
|
|
2175
|
+
modeAutoHint: "Toda nota de voz recebida \xE9 transcrita na hora. Mais c\xF4modo, consome mais cota.",
|
|
2176
|
+
modeOnDemand: "Quando o atendente pedir",
|
|
2177
|
+
modeOnDemandHint: "Transcreve s\xF3 ao clicar no bot\xE3o da conversa. Consome cota apenas com \xE1udio que algu\xE9m vai ler.",
|
|
2178
|
+
unavailable: "Transcri\xE7\xE3o n\xE3o est\xE1 dispon\xEDvel neste ambiente \u2014 fale com quem administra a instala\xE7\xE3o.",
|
|
2179
|
+
save: "Salvar",
|
|
2180
|
+
saving: "Salvando...",
|
|
2181
|
+
saveSuccess: "Configura\xE7\xE3o de transcri\xE7\xE3o salva."
|
|
2182
|
+
};
|
|
2183
|
+
function TranscriptionSettingsForm({
|
|
2184
|
+
enabled,
|
|
2185
|
+
onEnabledChange,
|
|
2186
|
+
mode,
|
|
2187
|
+
onModeChange,
|
|
2188
|
+
onSave,
|
|
2189
|
+
isAvailable = true,
|
|
2190
|
+
saving = false,
|
|
2191
|
+
saveSuccess = false,
|
|
2192
|
+
labels: labelsOverride
|
|
2193
|
+
}) {
|
|
2194
|
+
const labels = { ...DEFAULT_LABELS5, ...labelsOverride };
|
|
2195
|
+
return /* @__PURE__ */ jsxs16("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
2196
|
+
/* @__PURE__ */ jsx18("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: /* @__PURE__ */ jsxs16("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-1.5", children: [
|
|
2197
|
+
/* @__PURE__ */ jsx18(AudioLines, { size: 14, className: "text-blue-600 dark:text-blue-400" }),
|
|
2198
|
+
labels.sectionTitle
|
|
2199
|
+
] }) }),
|
|
2200
|
+
/* @__PURE__ */ jsxs16("form", { onSubmit: onSave, className: "p-6 space-y-5", children: [
|
|
2201
|
+
!isAvailable && /* @__PURE__ */ jsx18("div", { className: "bg-amber-50 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800 rounded-xl px-4 py-3 text-sm text-amber-800 dark:text-amber-300", children: labels.unavailable }),
|
|
2202
|
+
/* @__PURE__ */ jsxs16("label", { className: "flex items-start gap-3 cursor-pointer", children: [
|
|
2203
|
+
/* @__PURE__ */ jsx18(
|
|
2204
|
+
"input",
|
|
2205
|
+
{
|
|
2206
|
+
type: "checkbox",
|
|
2207
|
+
checked: enabled,
|
|
2208
|
+
onChange: (event) => onEnabledChange(event.target.checked),
|
|
2209
|
+
disabled: !isAvailable,
|
|
2210
|
+
className: "mt-0.5 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 disabled:opacity-50"
|
|
2211
|
+
}
|
|
2212
|
+
),
|
|
2213
|
+
/* @__PURE__ */ jsxs16("span", { children: [
|
|
2214
|
+
/* @__PURE__ */ jsx18("span", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300", children: labels.enabled }),
|
|
2215
|
+
/* @__PURE__ */ jsx18("span", { className: "block text-xs text-gray-500 dark:text-gray-400", children: labels.enabledHint })
|
|
2216
|
+
] })
|
|
2217
|
+
] }),
|
|
2218
|
+
enabled && /* @__PURE__ */ jsxs16("fieldset", { disabled: !isAvailable, className: "space-y-2.5", children: [
|
|
2219
|
+
/* @__PURE__ */ jsx18("legend", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: labels.modeTitle }),
|
|
2220
|
+
/* @__PURE__ */ jsx18(
|
|
2221
|
+
ModeOption,
|
|
2222
|
+
{
|
|
2223
|
+
value: "onDemand",
|
|
2224
|
+
current: mode,
|
|
2225
|
+
onSelect: onModeChange,
|
|
2226
|
+
title: labels.modeOnDemand,
|
|
2227
|
+
hint: labels.modeOnDemandHint
|
|
2228
|
+
}
|
|
2229
|
+
),
|
|
2230
|
+
/* @__PURE__ */ jsx18(
|
|
2231
|
+
ModeOption,
|
|
2232
|
+
{
|
|
2233
|
+
value: "auto",
|
|
2234
|
+
current: mode,
|
|
2235
|
+
onSelect: onModeChange,
|
|
2236
|
+
title: labels.modeAuto,
|
|
2237
|
+
hint: labels.modeAutoHint
|
|
2238
|
+
}
|
|
2239
|
+
)
|
|
2240
|
+
] }),
|
|
2241
|
+
saveSuccess && /* @__PURE__ */ jsx18("div", { className: "bg-green-50 dark:bg-green-950/40 border border-green-200 dark:border-green-800 rounded-xl px-4 py-3 text-sm text-green-700 dark:text-green-400", children: labels.saveSuccess }),
|
|
2242
|
+
/* @__PURE__ */ jsx18("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs16(
|
|
2243
|
+
"button",
|
|
2244
|
+
{
|
|
2245
|
+
type: "submit",
|
|
2246
|
+
disabled: saving || !isAvailable,
|
|
2247
|
+
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",
|
|
2248
|
+
children: [
|
|
2249
|
+
saving ? /* @__PURE__ */ jsx18("span", { className: "w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin" }) : /* @__PURE__ */ jsx18(Save3, { size: 14 }),
|
|
1806
2250
|
saving ? labels.saving : labels.save
|
|
1807
2251
|
]
|
|
1808
2252
|
}
|
|
@@ -1810,11 +2254,90 @@ function WelcomeFarewellForm({
|
|
|
1810
2254
|
] })
|
|
1811
2255
|
] });
|
|
1812
2256
|
}
|
|
2257
|
+
function ModeOption({
|
|
2258
|
+
value,
|
|
2259
|
+
current,
|
|
2260
|
+
onSelect,
|
|
2261
|
+
title,
|
|
2262
|
+
hint
|
|
2263
|
+
}) {
|
|
2264
|
+
const isSelected = current === value;
|
|
2265
|
+
return /* @__PURE__ */ jsxs16(
|
|
2266
|
+
"label",
|
|
2267
|
+
{
|
|
2268
|
+
className: `flex items-start gap-3 cursor-pointer rounded-xl border px-4 py-3 transition-colors ${isSelected ? "border-blue-500 bg-blue-50 dark:border-blue-500 dark:bg-blue-950/30" : "border-gray-200 hover:border-gray-300 dark:border-gray-700 dark:hover:border-gray-600"}`,
|
|
2269
|
+
children: [
|
|
2270
|
+
/* @__PURE__ */ jsx18(
|
|
2271
|
+
"input",
|
|
2272
|
+
{
|
|
2273
|
+
type: "radio",
|
|
2274
|
+
name: "transcription-mode",
|
|
2275
|
+
value,
|
|
2276
|
+
checked: isSelected,
|
|
2277
|
+
onChange: () => onSelect(value),
|
|
2278
|
+
className: "mt-0.5 h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
2279
|
+
}
|
|
2280
|
+
),
|
|
2281
|
+
/* @__PURE__ */ jsxs16("span", { children: [
|
|
2282
|
+
/* @__PURE__ */ jsx18("span", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300", children: title }),
|
|
2283
|
+
/* @__PURE__ */ jsx18("span", { className: "block text-xs text-gray-500 dark:text-gray-400", children: hint })
|
|
2284
|
+
] })
|
|
2285
|
+
]
|
|
2286
|
+
}
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
// src/settings/WhatsAppTemplatesSettings.tsx
|
|
2291
|
+
import { useState as useState9 } from "react";
|
|
2292
|
+
import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2293
|
+
var TEMPLATE_SETTINGS_TAB = {
|
|
2294
|
+
SELECT: "select",
|
|
2295
|
+
CREATE: "create"
|
|
2296
|
+
};
|
|
2297
|
+
var DEFAULT_TEMPLATES_SETTINGS_LABELS = {
|
|
2298
|
+
selectTab: "Template de reengajamento",
|
|
2299
|
+
createTab: "Criar template"
|
|
2300
|
+
};
|
|
2301
|
+
function WhatsAppTemplatesSettings({
|
|
2302
|
+
labels: labelsOverride,
|
|
2303
|
+
create,
|
|
2304
|
+
...settingsProps
|
|
2305
|
+
}) {
|
|
2306
|
+
const labels = { ...DEFAULT_TEMPLATES_SETTINGS_LABELS, ...labelsOverride };
|
|
2307
|
+
const [tab, setTab] = useState9(TEMPLATE_SETTINGS_TAB.SELECT);
|
|
2308
|
+
return /* @__PURE__ */ jsxs17("div", { className: "space-y-4", children: [
|
|
2309
|
+
/* @__PURE__ */ jsxs17("nav", { className: "flex gap-1 border-b", role: "tablist", children: [
|
|
2310
|
+
/* @__PURE__ */ jsx19(
|
|
2311
|
+
"button",
|
|
2312
|
+
{
|
|
2313
|
+
type: "button",
|
|
2314
|
+
role: "tab",
|
|
2315
|
+
"aria-selected": tab === TEMPLATE_SETTINGS_TAB.SELECT,
|
|
2316
|
+
onClick: () => setTab(TEMPLATE_SETTINGS_TAB.SELECT),
|
|
2317
|
+
className: `cv-subtab ${tab === TEMPLATE_SETTINGS_TAB.SELECT ? "cv-subtab--active" : ""}`,
|
|
2318
|
+
children: labels.selectTab
|
|
2319
|
+
}
|
|
2320
|
+
),
|
|
2321
|
+
create ? /* @__PURE__ */ jsx19(
|
|
2322
|
+
"button",
|
|
2323
|
+
{
|
|
2324
|
+
type: "button",
|
|
2325
|
+
role: "tab",
|
|
2326
|
+
"aria-selected": tab === TEMPLATE_SETTINGS_TAB.CREATE,
|
|
2327
|
+
onClick: () => setTab(TEMPLATE_SETTINGS_TAB.CREATE),
|
|
2328
|
+
className: `cv-subtab ${tab === TEMPLATE_SETTINGS_TAB.CREATE ? "cv-subtab--active" : ""}`,
|
|
2329
|
+
children: labels.createTab
|
|
2330
|
+
}
|
|
2331
|
+
) : null
|
|
2332
|
+
] }),
|
|
2333
|
+
tab === TEMPLATE_SETTINGS_TAB.SELECT ? /* @__PURE__ */ jsx19(WhatsAppTemplateSettingsForm, { ...settingsProps }) : create ? /* @__PURE__ */ jsx19(WhatsAppCreateTemplateForm, { ...create }) : null
|
|
2334
|
+
] });
|
|
2335
|
+
}
|
|
1813
2336
|
|
|
1814
2337
|
// src/settings/TopicsForm.tsx
|
|
1815
|
-
import { Megaphone, Save as
|
|
1816
|
-
import { jsx as
|
|
1817
|
-
var
|
|
2338
|
+
import { Megaphone, Save as Save4 } from "lucide-react";
|
|
2339
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2340
|
+
var DEFAULT_LABELS6 = {
|
|
1818
2341
|
sectionTitle: "T\xF3picos intermedi\xE1rios",
|
|
1819
2342
|
sectionDescription: "Mensagens autom\xE1ticas para assuntos espec\xEDficos, disparadas quando o cliente demonstra interesse.",
|
|
1820
2343
|
messagePlaceholder: "Digite a mensagem autom\xE1tica para este t\xF3pico...",
|
|
@@ -1830,20 +2353,20 @@ function TopicsForm({
|
|
|
1830
2353
|
saveSuccess = false,
|
|
1831
2354
|
labels: labelsOverride
|
|
1832
2355
|
}) {
|
|
1833
|
-
const labels = { ...
|
|
1834
|
-
return /* @__PURE__ */
|
|
1835
|
-
/* @__PURE__ */
|
|
1836
|
-
/* @__PURE__ */
|
|
1837
|
-
/* @__PURE__ */
|
|
2356
|
+
const labels = { ...DEFAULT_LABELS6, ...labelsOverride };
|
|
2357
|
+
return /* @__PURE__ */ jsxs18("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
2358
|
+
/* @__PURE__ */ jsxs18("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
2359
|
+
/* @__PURE__ */ jsxs18("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
2360
|
+
/* @__PURE__ */ jsx20(Megaphone, { size: 16, className: "text-orange-600 dark:text-orange-400" }),
|
|
1838
2361
|
labels.sectionTitle
|
|
1839
2362
|
] }),
|
|
1840
|
-
/* @__PURE__ */
|
|
2363
|
+
/* @__PURE__ */ jsx20("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: labels.sectionDescription })
|
|
1841
2364
|
] }),
|
|
1842
|
-
/* @__PURE__ */
|
|
1843
|
-
topics.map((topic) => /* @__PURE__ */
|
|
1844
|
-
/* @__PURE__ */
|
|
1845
|
-
/* @__PURE__ */
|
|
1846
|
-
/* @__PURE__ */
|
|
2365
|
+
/* @__PURE__ */ jsxs18("form", { onSubmit: onSave, className: "p-6 space-y-6", children: [
|
|
2366
|
+
topics.map((topic) => /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
2367
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex items-start gap-4", children: [
|
|
2368
|
+
/* @__PURE__ */ jsx20("div", { className: "flex-1", children: /* @__PURE__ */ jsx20("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: topic.label }) }),
|
|
2369
|
+
/* @__PURE__ */ jsx20(
|
|
1847
2370
|
"button",
|
|
1848
2371
|
{
|
|
1849
2372
|
type: "button",
|
|
@@ -1851,7 +2374,7 @@ function TopicsForm({
|
|
|
1851
2374
|
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
2375
|
role: "switch",
|
|
1853
2376
|
"aria-checked": topic.enabled,
|
|
1854
|
-
children: /* @__PURE__ */
|
|
2377
|
+
children: /* @__PURE__ */ jsx20(
|
|
1855
2378
|
"span",
|
|
1856
2379
|
{
|
|
1857
2380
|
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 +2383,7 @@ function TopicsForm({
|
|
|
1860
2383
|
}
|
|
1861
2384
|
)
|
|
1862
2385
|
] }),
|
|
1863
|
-
/* @__PURE__ */
|
|
2386
|
+
/* @__PURE__ */ jsx20(
|
|
1864
2387
|
"textarea",
|
|
1865
2388
|
{
|
|
1866
2389
|
value: topic.message,
|
|
@@ -1872,55 +2395,27 @@ function TopicsForm({
|
|
|
1872
2395
|
}
|
|
1873
2396
|
)
|
|
1874
2397
|
] }, topic.key)),
|
|
1875
|
-
/* @__PURE__ */
|
|
1876
|
-
/* @__PURE__ */
|
|
2398
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-3", children: [
|
|
2399
|
+
/* @__PURE__ */ jsxs18(
|
|
1877
2400
|
"button",
|
|
1878
2401
|
{
|
|
1879
2402
|
type: "submit",
|
|
1880
2403
|
disabled: saving,
|
|
1881
2404
|
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
2405
|
children: [
|
|
1883
|
-
/* @__PURE__ */
|
|
2406
|
+
/* @__PURE__ */ jsx20(Save4, { size: 14 }),
|
|
1884
2407
|
labels.saveButton
|
|
1885
2408
|
]
|
|
1886
2409
|
}
|
|
1887
2410
|
),
|
|
1888
|
-
saveSuccess && /* @__PURE__ */
|
|
2411
|
+
saveSuccess && /* @__PURE__ */ jsx20("span", { className: "text-sm text-green-600 dark:text-green-400", children: labels.saveSuccess })
|
|
1889
2412
|
] })
|
|
1890
2413
|
] })
|
|
1891
2414
|
] });
|
|
1892
2415
|
}
|
|
1893
2416
|
|
|
1894
2417
|
// 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
|
|
2418
|
+
import { useCallback as useCallback5 } from "react";
|
|
1924
2419
|
function useConversationMessages(conversationId, params) {
|
|
1925
2420
|
const context = useConversations();
|
|
1926
2421
|
if (!context) {
|
|
@@ -1931,7 +2426,7 @@ function useConversationMessages(conversationId, params) {
|
|
|
1931
2426
|
() => api.fetchMessages(conversationId, params),
|
|
1932
2427
|
[conversationId, params?.limit, params?.before]
|
|
1933
2428
|
);
|
|
1934
|
-
const sendMessage =
|
|
2429
|
+
const sendMessage = useCallback5(
|
|
1935
2430
|
async (text) => {
|
|
1936
2431
|
const message = await api.sendMessage(conversationId, text);
|
|
1937
2432
|
await refetch();
|
|
@@ -1939,7 +2434,7 @@ function useConversationMessages(conversationId, params) {
|
|
|
1939
2434
|
},
|
|
1940
2435
|
[api, conversationId, refetch]
|
|
1941
2436
|
);
|
|
1942
|
-
const sendMedia =
|
|
2437
|
+
const sendMedia = useCallback5(
|
|
1943
2438
|
async (mediaData) => {
|
|
1944
2439
|
const message = await api.sendMedia(conversationId, mediaData);
|
|
1945
2440
|
await refetch();
|
|
@@ -1947,17 +2442,57 @@ function useConversationMessages(conversationId, params) {
|
|
|
1947
2442
|
},
|
|
1948
2443
|
[api, conversationId, refetch]
|
|
1949
2444
|
);
|
|
1950
|
-
const sendTemplate =
|
|
2445
|
+
const sendTemplate = useCallback5(
|
|
1951
2446
|
async (templateData) => {
|
|
1952
2447
|
await api.sendTemplate(conversationId, templateData);
|
|
1953
2448
|
await refetch();
|
|
1954
2449
|
},
|
|
1955
2450
|
[api, conversationId, refetch]
|
|
1956
2451
|
);
|
|
1957
|
-
const markRead =
|
|
2452
|
+
const markRead = useCallback5(() => api.markRead(conversationId), [api, conversationId]);
|
|
1958
2453
|
return { messages: data ?? [], loading, error, refetch, sendMessage, sendMedia, sendTemplate, markRead };
|
|
1959
2454
|
}
|
|
1960
2455
|
|
|
2456
|
+
// src/hooks/useScrollToLatestMessage.ts
|
|
2457
|
+
import { useCallback as useCallback6, useLayoutEffect, useRef as useRef5, useState as useState10 } from "react";
|
|
2458
|
+
var NEAR_BOTTOM_THRESHOLD_PX = 120;
|
|
2459
|
+
var SCROLL_BEHAVIOR = "auto";
|
|
2460
|
+
function useScrollToLatestMessage({
|
|
2461
|
+
conversationId,
|
|
2462
|
+
messageCount
|
|
2463
|
+
}) {
|
|
2464
|
+
const containerRef = useRef5(null);
|
|
2465
|
+
const [isAwayFromBottom, setIsAwayFromBottom] = useState10(false);
|
|
2466
|
+
const isAwayFromBottomRef = useRef5(false);
|
|
2467
|
+
const scrollToBottom = useCallback6((behavior = SCROLL_BEHAVIOR) => {
|
|
2468
|
+
const container = containerRef.current;
|
|
2469
|
+
if (!container) return;
|
|
2470
|
+
container.scrollTo({ top: container.scrollHeight, behavior });
|
|
2471
|
+
}, []);
|
|
2472
|
+
const handleScroll = useCallback6((event) => {
|
|
2473
|
+
const target = event.currentTarget;
|
|
2474
|
+
const distanceFromBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
|
|
2475
|
+
const away = distanceFromBottom > NEAR_BOTTOM_THRESHOLD_PX;
|
|
2476
|
+
isAwayFromBottomRef.current = away;
|
|
2477
|
+
setIsAwayFromBottom((current) => current === away ? current : away);
|
|
2478
|
+
}, []);
|
|
2479
|
+
const jumpedForConversationRef = useRef5(void 0);
|
|
2480
|
+
useLayoutEffect(() => {
|
|
2481
|
+
const isNewConversation = jumpedForConversationRef.current !== conversationId;
|
|
2482
|
+
if (isNewConversation) {
|
|
2483
|
+
isAwayFromBottomRef.current = false;
|
|
2484
|
+
setIsAwayFromBottom(false);
|
|
2485
|
+
if (messageCount === 0) return;
|
|
2486
|
+
jumpedForConversationRef.current = conversationId;
|
|
2487
|
+
scrollToBottom(SCROLL_BEHAVIOR);
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
if (isAwayFromBottomRef.current) return;
|
|
2491
|
+
scrollToBottom(SCROLL_BEHAVIOR);
|
|
2492
|
+
}, [conversationId, messageCount, scrollToBottom]);
|
|
2493
|
+
return { containerRef, handleScroll, isAwayFromBottom, scrollToBottom };
|
|
2494
|
+
}
|
|
2495
|
+
|
|
1961
2496
|
// src/hooks/useConversationList.ts
|
|
1962
2497
|
function useConversationList(params) {
|
|
1963
2498
|
const context = useConversations();
|
|
@@ -1965,11 +2500,15 @@ function useConversationList(params) {
|
|
|
1965
2500
|
throw new Error("useConversationList requires an ancestor <ConversationsProvider>");
|
|
1966
2501
|
}
|
|
1967
2502
|
const { api } = context;
|
|
2503
|
+
const filtersKey = JSON.stringify(params?.filters ?? {});
|
|
1968
2504
|
const { data, loading, error, refetch } = useAsyncResource(
|
|
1969
2505
|
() => api.fetchConversations(params),
|
|
1970
|
-
[params?.page, params?.limit, params?.waitingHuman, params?.search]
|
|
2506
|
+
[params?.page, params?.limit, params?.waitingHuman, params?.search, filtersKey]
|
|
1971
2507
|
);
|
|
1972
|
-
|
|
2508
|
+
if (data === void 0) {
|
|
2509
|
+
return { conversations: [], total: 0, loading, error, refetch };
|
|
2510
|
+
}
|
|
2511
|
+
return { conversations: conversationsOf(data), total: totalOf(data), loading, error, refetch };
|
|
1973
2512
|
}
|
|
1974
2513
|
|
|
1975
2514
|
// src/hooks/useConversationContext.ts
|
|
@@ -1983,25 +2522,11 @@ function useConversationContext(conversationId) {
|
|
|
1983
2522
|
return { context: data, loading, error, refetch };
|
|
1984
2523
|
}
|
|
1985
2524
|
|
|
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
2525
|
// src/hooks/useConversationRealtime.ts
|
|
2001
|
-
import { useEffect as useEffect6, useRef as
|
|
2526
|
+
import { useEffect as useEffect6, useRef as useRef6 } from "react";
|
|
2002
2527
|
function useConversationRealtime(conversationId, onEvent) {
|
|
2003
2528
|
const context = useConversations();
|
|
2004
|
-
const onEventRef =
|
|
2529
|
+
const onEventRef = useRef6(onEvent);
|
|
2005
2530
|
onEventRef.current = onEvent;
|
|
2006
2531
|
useEffect6(() => {
|
|
2007
2532
|
if (!context || !conversationId) return;
|
|
@@ -2016,7 +2541,7 @@ function useConversationRealtime(conversationId, onEvent) {
|
|
|
2016
2541
|
}
|
|
2017
2542
|
function useGlobalRealtime(onEvent) {
|
|
2018
2543
|
const context = useConversations();
|
|
2019
|
-
const onEventRef =
|
|
2544
|
+
const onEventRef = useRef6(onEvent);
|
|
2020
2545
|
onEventRef.current = onEvent;
|
|
2021
2546
|
useEffect6(() => {
|
|
2022
2547
|
if (!context) return;
|
|
@@ -2029,16 +2554,83 @@ function useGlobalRealtime(onEvent) {
|
|
|
2029
2554
|
};
|
|
2030
2555
|
}, [context]);
|
|
2031
2556
|
}
|
|
2557
|
+
|
|
2558
|
+
// src/hooks/useConversationActions.ts
|
|
2559
|
+
import { useMemo as useMemo2 } from "react";
|
|
2560
|
+
function useConversationActions(conversationId) {
|
|
2561
|
+
const context = useConversations();
|
|
2562
|
+
if (!context) {
|
|
2563
|
+
throw new Error("useConversationActions requires an ancestor <ConversationsProvider>");
|
|
2564
|
+
}
|
|
2565
|
+
const { api } = context;
|
|
2566
|
+
return useMemo2(
|
|
2567
|
+
() => ({
|
|
2568
|
+
takeover: api.takeover ? () => api.takeover(conversationId) : void 0,
|
|
2569
|
+
release: api.release ? () => api.release(conversationId) : void 0,
|
|
2570
|
+
finalize: api.finalize ? () => api.finalize(conversationId) : void 0
|
|
2571
|
+
}),
|
|
2572
|
+
[api, conversationId]
|
|
2573
|
+
);
|
|
2574
|
+
}
|
|
2575
|
+
function useInboxActions() {
|
|
2576
|
+
const context = useConversations();
|
|
2577
|
+
if (!context) {
|
|
2578
|
+
throw new Error("useInboxActions requires an ancestor <ConversationsProvider>");
|
|
2579
|
+
}
|
|
2580
|
+
const { api } = context;
|
|
2581
|
+
return useMemo2(
|
|
2582
|
+
() => ({
|
|
2583
|
+
markAllRead: api.markAllRead ? () => api.markAllRead() : void 0,
|
|
2584
|
+
listTemplates: api.listTemplates ? () => api.listTemplates() : void 0
|
|
2585
|
+
}),
|
|
2586
|
+
[api]
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2032
2589
|
export {
|
|
2033
2590
|
AudioPlayer,
|
|
2591
|
+
AudioRecorderButton,
|
|
2592
|
+
AudioTranscription,
|
|
2034
2593
|
Avatar,
|
|
2594
|
+
CHANNEL_BRAND_COLOR,
|
|
2595
|
+
CHANNEL_CAPABILITIES,
|
|
2596
|
+
CHANNEL_FILTER_ALL,
|
|
2597
|
+
CONVERSATION_CHANNEL,
|
|
2598
|
+
CONVERSATION_WINDOW,
|
|
2599
|
+
ChannelIcon,
|
|
2600
|
+
ConversationContextPanel,
|
|
2601
|
+
ConversationDocumentsPanel,
|
|
2602
|
+
ConversationHeader,
|
|
2035
2603
|
ConversationListItem,
|
|
2036
2604
|
ConversationLocalesProvider,
|
|
2605
|
+
ConversationRow,
|
|
2037
2606
|
ConversationWallpaper,
|
|
2038
2607
|
ConversationsProvider,
|
|
2608
|
+
DEFAULT_ACCEPTED_FILE_TYPES,
|
|
2609
|
+
DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
|
|
2610
|
+
DEFAULT_AVATAR_LABELS,
|
|
2611
|
+
DEFAULT_CONVERSATION_CHANNEL,
|
|
2612
|
+
DEFAULT_CONVERSATION_CONTEXT_LABELS,
|
|
2613
|
+
DEFAULT_CONVERSATION_DOCUMENTS_LABELS,
|
|
2614
|
+
DEFAULT_CONVERSATION_HEADER_LABELS,
|
|
2615
|
+
DEFAULT_CONVERSATION_LIST_ITEM_LABELS,
|
|
2616
|
+
DEFAULT_DOCUMENTS_LIBRARY_LABELS,
|
|
2617
|
+
DEFAULT_EMOJI_PICKER_LABELS,
|
|
2618
|
+
DEFAULT_INTERACTIVE_MESSAGE_LABELS,
|
|
2619
|
+
DEFAULT_LIGHTBOX_LABELS,
|
|
2620
|
+
DEFAULT_MAX_RECORDING_MILLISECONDS,
|
|
2621
|
+
DEFAULT_MESSAGE_COMPOSER_LABELS,
|
|
2622
|
+
DEFAULT_RICH_COMPOSER_TOOLTIPS,
|
|
2623
|
+
DEFAULT_TEMPLATES_SETTINGS_LABELS,
|
|
2624
|
+
DEFAULT_WHATSAPP_MESSAGE_EDITOR_LABELS,
|
|
2625
|
+
DEFAULT_WINDOW_EXPIRED_LABELS,
|
|
2626
|
+
DOCUMENT_SOURCE_FILTER,
|
|
2039
2627
|
DateDivider,
|
|
2628
|
+
DocumentsLibrary,
|
|
2629
|
+
EMOJI_CATEGORIES,
|
|
2040
2630
|
EmojiPicker,
|
|
2041
2631
|
FileIcon,
|
|
2632
|
+
HANDLE_KIND,
|
|
2633
|
+
InteractiveMessage,
|
|
2042
2634
|
Lightbox,
|
|
2043
2635
|
MediaRenderer,
|
|
2044
2636
|
MessageBubble,
|
|
@@ -2046,21 +2638,46 @@ export {
|
|
|
2046
2638
|
MessageTail,
|
|
2047
2639
|
MessageText,
|
|
2048
2640
|
MessageTimestamp,
|
|
2641
|
+
NARROW_MAX_WIDTH_PX,
|
|
2642
|
+
REOPEN_MECHANISM,
|
|
2643
|
+
RICH_COMPOSER_ACTION,
|
|
2644
|
+
RichMessageComposer,
|
|
2049
2645
|
SimpleEmojiPicker,
|
|
2050
2646
|
StatusTicks,
|
|
2647
|
+
TEMPLATE_SETTINGS_TAB,
|
|
2051
2648
|
ToastProvider,
|
|
2052
2649
|
TopicsForm,
|
|
2650
|
+
TranscriptionSettingsForm,
|
|
2651
|
+
WINDOW_FILTERS,
|
|
2053
2652
|
WelcomeFarewellForm,
|
|
2054
2653
|
WhatsAppCreateTemplateForm,
|
|
2055
2654
|
WhatsAppMessageEditor,
|
|
2056
2655
|
WhatsAppTemplateSettingsForm,
|
|
2656
|
+
WhatsAppTemplatesSettings,
|
|
2657
|
+
WindowExpiredNotice,
|
|
2658
|
+
applyQuickReplyVariables,
|
|
2659
|
+
buildTranscriptFilename,
|
|
2660
|
+
buildTranscriptText,
|
|
2661
|
+
capabilitiesOf,
|
|
2662
|
+
channelFiltersFor,
|
|
2663
|
+
contactFlag,
|
|
2664
|
+
createMediaUrlResolver,
|
|
2665
|
+
downloadTextFile,
|
|
2666
|
+
formatContactHandle,
|
|
2667
|
+
formatDateTime,
|
|
2057
2668
|
formatFileSize,
|
|
2058
2669
|
formatPhone,
|
|
2670
|
+
formatStalledFor,
|
|
2059
2671
|
formatTimestamp,
|
|
2060
2672
|
htmlToWA,
|
|
2673
|
+
isSameDay,
|
|
2674
|
+
isWindowBlocking,
|
|
2061
2675
|
parseWhatsAppFormatting,
|
|
2062
2676
|
phoneInitials,
|
|
2677
|
+
resolveQuickReply,
|
|
2678
|
+
searchEmojis,
|
|
2063
2679
|
toast,
|
|
2680
|
+
useConversationActions,
|
|
2064
2681
|
useConversationContext,
|
|
2065
2682
|
useConversationDocuments,
|
|
2066
2683
|
useConversationList,
|
|
@@ -2070,8 +2687,13 @@ export {
|
|
|
2070
2687
|
useConversations,
|
|
2071
2688
|
useDarkMode,
|
|
2072
2689
|
useGlobalRealtime,
|
|
2690
|
+
useInboxActions,
|
|
2691
|
+
useIsDarkTheme,
|
|
2692
|
+
useIsNarrow,
|
|
2693
|
+
useScrollToLatestMessage,
|
|
2073
2694
|
useToast,
|
|
2074
2695
|
useWaitingNotifications,
|
|
2075
2696
|
waToHTML,
|
|
2076
|
-
waToHTMLInline
|
|
2697
|
+
waToHTMLInline,
|
|
2698
|
+
windowOf
|
|
2077
2699
|
};
|