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