@adatechnology/conversations-ui 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConversationSimulatorPanel-DmV8uaFE.d.ts → ConversationSimulatorPanel-DnXElfY8.d.ts} +87 -73
- package/dist/{chunk-L4OE4ORQ.js → chunk-4P5KMWOI.js} +219 -161
- package/dist/index.d.ts +51 -6
- package/dist/index.js +273 -135
- package/dist/preview/index.d.ts +2 -2
- package/dist/preview/index.js +4 -2
- package/dist/styles.css +16 -0
- package/package.json +3 -2
- package/src/AudioRecorderButton.test.tsx +18 -0
- package/src/AudioRecorderButton.tsx +17 -2
- package/src/ChannelIcon.tsx +11 -0
- package/src/MessageBubble.test.tsx +33 -0
- package/src/MessageBubble.tsx +18 -5
- package/src/MessageComposer.test.tsx +39 -1
- package/src/MessageComposer.tsx +66 -15
- package/src/channelCapability.test.ts +47 -0
- package/src/channelCapability.ts +37 -0
- package/src/conversationChannel.ts +19 -10
- package/src/conversationWindow.ts +7 -5
- package/src/hooks/useConversationMessages.test.ts +90 -0
- package/src/hooks/useConversationMessages.ts +116 -2
- package/src/hooks/useLoadOlderMessagesScroll.test.ts +21 -0
- package/src/hooks/useLoadOlderMessagesScroll.ts +76 -0
- package/src/index.ts +5 -1
- package/src/preview/ConversationSimulatorPanel.tsx +4 -0
- package/src/preview/createMockConversationsApi.ts +5 -1
- package/src/styles.css +19 -0
- package/src/workspace/ConversationPane.tsx +32 -5
- package/src/workspace/labels.ts +4 -0
|
@@ -64,6 +64,147 @@ function useConversationLocales() {
|
|
|
64
64
|
return useContext(ConversationLocalesContext);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// src/lib/phone.ts
|
|
68
|
+
function formatPhone(number) {
|
|
69
|
+
const digits = number.replace(/\D/g, "");
|
|
70
|
+
if (digits.length === 13) {
|
|
71
|
+
return `+${digits.slice(0, 2)} (${digits.slice(2, 4)}) ${digits.slice(4, 9)}-${digits.slice(9, 13)}`;
|
|
72
|
+
}
|
|
73
|
+
if (digits.length === 12) {
|
|
74
|
+
return `+${digits.slice(0, 2)} (${digits.slice(2, 4)}) ${digits.slice(4, 8)}-${digits.slice(8, 12)}`;
|
|
75
|
+
}
|
|
76
|
+
if (digits.length === 11) {
|
|
77
|
+
return `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7, 11)}`;
|
|
78
|
+
}
|
|
79
|
+
if (digits.length === 10) {
|
|
80
|
+
return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)}-${digits.slice(6, 10)}`;
|
|
81
|
+
}
|
|
82
|
+
return number;
|
|
83
|
+
}
|
|
84
|
+
var COUNTRY_FLAG_BY_DIAL_CODE = {
|
|
85
|
+
"55": "\u{1F1E7}\u{1F1F7}",
|
|
86
|
+
"351": "\u{1F1F5}\u{1F1F9}",
|
|
87
|
+
"34": "\u{1F1EA}\u{1F1F8}",
|
|
88
|
+
"54": "\u{1F1E6}\u{1F1F7}",
|
|
89
|
+
"56": "\u{1F1E8}\u{1F1F1}",
|
|
90
|
+
"57": "\u{1F1E8}\u{1F1F4}",
|
|
91
|
+
"52": "\u{1F1F2}\u{1F1FD}",
|
|
92
|
+
"598": "\u{1F1FA}\u{1F1FE}",
|
|
93
|
+
"595": "\u{1F1F5}\u{1F1FE}",
|
|
94
|
+
"44": "\u{1F1EC}\u{1F1E7}",
|
|
95
|
+
"49": "\u{1F1E9}\u{1F1EA}",
|
|
96
|
+
"39": "\u{1F1EE}\u{1F1F9}",
|
|
97
|
+
"33": "\u{1F1EB}\u{1F1F7}"
|
|
98
|
+
};
|
|
99
|
+
function phoneCountryFlag(number) {
|
|
100
|
+
const digits = number.replace(/\D/g, "");
|
|
101
|
+
for (const length of [3, 2, 1]) {
|
|
102
|
+
const flag = COUNTRY_FLAG_BY_DIAL_CODE[digits.slice(0, length)];
|
|
103
|
+
if (flag) return flag;
|
|
104
|
+
}
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
function phoneInitials(number) {
|
|
108
|
+
const digits = number.replace(/\D/g, "");
|
|
109
|
+
return digits.slice(-2);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/conversationChannel.ts
|
|
113
|
+
var CONVERSATION_CHANNEL = {
|
|
114
|
+
WHATSAPP: "whatsapp",
|
|
115
|
+
MESSENGER: "messenger",
|
|
116
|
+
INSTAGRAM: "instagram",
|
|
117
|
+
WEBCHAT: "webchat",
|
|
118
|
+
EMAIL: "email"
|
|
119
|
+
};
|
|
120
|
+
var DEFAULT_CONVERSATION_CHANNEL = CONVERSATION_CHANNEL.WHATSAPP;
|
|
121
|
+
var REOPEN_MECHANISM = {
|
|
122
|
+
TEMPLATE: "template",
|
|
123
|
+
TAG: "tag",
|
|
124
|
+
NONE: "none"
|
|
125
|
+
};
|
|
126
|
+
var HANDLE_KIND = {
|
|
127
|
+
PHONE: "phone",
|
|
128
|
+
USERNAME: "username",
|
|
129
|
+
SESSION: "session",
|
|
130
|
+
EMAIL: "email"
|
|
131
|
+
};
|
|
132
|
+
var CHANNEL_CAPABILITIES = {
|
|
133
|
+
[CONVERSATION_CHANNEL.WHATSAPP]: {
|
|
134
|
+
label: "WhatsApp",
|
|
135
|
+
icon: "\u{1F4AC}",
|
|
136
|
+
reopenMechanism: REOPEN_MECHANISM.TEMPLATE,
|
|
137
|
+
handleKind: HANDLE_KIND.PHONE
|
|
138
|
+
},
|
|
139
|
+
[CONVERSATION_CHANNEL.MESSENGER]: {
|
|
140
|
+
// Messenger também tem 24h, mas reabre com message tag — não com template aprovado.
|
|
141
|
+
label: "Messenger",
|
|
142
|
+
icon: "\u{1F4E8}",
|
|
143
|
+
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
144
|
+
handleKind: HANDLE_KIND.USERNAME
|
|
145
|
+
},
|
|
146
|
+
[CONVERSATION_CHANNEL.INSTAGRAM]: {
|
|
147
|
+
label: "Instagram",
|
|
148
|
+
icon: "\u{1F4F7}",
|
|
149
|
+
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
150
|
+
handleKind: HANDLE_KIND.USERNAME
|
|
151
|
+
},
|
|
152
|
+
[CONVERSATION_CHANNEL.WEBCHAT]: {
|
|
153
|
+
// Chat próprio: sem intermediário, sem janela. Bloquear o composer aqui seria inventar limite.
|
|
154
|
+
label: "Chat do site",
|
|
155
|
+
icon: "\u{1F310}",
|
|
156
|
+
reopenMechanism: REOPEN_MECHANISM.NONE,
|
|
157
|
+
handleKind: HANDLE_KIND.SESSION
|
|
158
|
+
},
|
|
159
|
+
[CONVERSATION_CHANNEL.EMAIL]: {
|
|
160
|
+
// RF10: e-mail entra no vocabulário; ele não reabre por template nem por tag — é só resposta.
|
|
161
|
+
label: "E-mail",
|
|
162
|
+
icon: "\u2709\uFE0F",
|
|
163
|
+
reopenMechanism: REOPEN_MECHANISM.NONE,
|
|
164
|
+
handleKind: HANDLE_KIND.EMAIL
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
function capabilitiesOf(channel) {
|
|
168
|
+
return CHANNEL_CAPABILITIES[channel ?? DEFAULT_CONVERSATION_CHANNEL];
|
|
169
|
+
}
|
|
170
|
+
var CHANNEL_FILTER_ALL = "all";
|
|
171
|
+
function channelFiltersFor(conversations) {
|
|
172
|
+
const present = new Set(
|
|
173
|
+
conversations.map((conversation) => conversation.channel ?? DEFAULT_CONVERSATION_CHANNEL)
|
|
174
|
+
);
|
|
175
|
+
if (present.size < 2) return [];
|
|
176
|
+
const ordered = Object.keys(CHANNEL_CAPABILITIES).filter((channel) => present.has(channel));
|
|
177
|
+
return [
|
|
178
|
+
{ value: CHANNEL_FILTER_ALL, label: "Todos" },
|
|
179
|
+
...ordered.map((channel) => ({ value: channel, label: CHANNEL_CAPABILITIES[channel].label }))
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
function formatContactHandle(params) {
|
|
183
|
+
const { handleKind } = capabilitiesOf(params.channel);
|
|
184
|
+
if (handleKind === HANDLE_KIND.PHONE) return formatPhone(params.handle);
|
|
185
|
+
if (handleKind === HANDLE_KIND.USERNAME) return params.handle.startsWith("@") ? params.handle : `@${params.handle}`;
|
|
186
|
+
if (handleKind === HANDLE_KIND.EMAIL) return params.handle;
|
|
187
|
+
return `Visitante ${params.handle.slice(-6)}`;
|
|
188
|
+
}
|
|
189
|
+
function contactFlag(params) {
|
|
190
|
+
return capabilitiesOf(params.channel).handleKind === HANDLE_KIND.PHONE ? phoneCountryFlag(params.handle) : "";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/channelCapability.ts
|
|
194
|
+
import {
|
|
195
|
+
CHANNEL_CAPABILITIES as CORE_CHANNEL_CAPABILITIES,
|
|
196
|
+
CONVERSATION_CHANNEL as CORE_CONVERSATION_CHANNEL
|
|
197
|
+
} from "@adatechnology/conversation-contracts";
|
|
198
|
+
var CORE_CHANNELS = CORE_CONVERSATION_CHANNEL;
|
|
199
|
+
function isCoreChannel(channel) {
|
|
200
|
+
return CORE_CHANNELS.includes(channel);
|
|
201
|
+
}
|
|
202
|
+
var META_ONLY_CHANNEL_CAPABILITY = CORE_CHANNEL_CAPABILITIES.whatsapp;
|
|
203
|
+
function channelCapabilityFor(channel) {
|
|
204
|
+
const resolved = channel ?? DEFAULT_CONVERSATION_CHANNEL;
|
|
205
|
+
return isCoreChannel(resolved) ? CORE_CHANNEL_CAPABILITIES[resolved] : META_ONLY_CHANNEL_CAPABILITY;
|
|
206
|
+
}
|
|
207
|
+
|
|
67
208
|
// src/StatusTicks.tsx
|
|
68
209
|
import { AlertTriangle } from "lucide-react";
|
|
69
210
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
@@ -757,8 +898,11 @@ function MessageBubble({
|
|
|
757
898
|
onResolveMediaUrl,
|
|
758
899
|
onInteractiveSelect,
|
|
759
900
|
onTranscribeAudio,
|
|
901
|
+
channel,
|
|
760
902
|
className
|
|
761
903
|
}) {
|
|
904
|
+
const confirmsRead = channelCapabilityFor(channel).confirmsRead;
|
|
905
|
+
const displayStatus = message.status === "read" && !confirmsRead ? "delivered" : message.status;
|
|
762
906
|
const { bubble, selection } = useConversationLocales();
|
|
763
907
|
const [lightboxSrc, setLightboxSrc] = useState5(null);
|
|
764
908
|
const context = useConversations();
|
|
@@ -781,7 +925,7 @@ function MessageBubble({
|
|
|
781
925
|
return trimmed && trimmed !== message.filename;
|
|
782
926
|
}) : void 0;
|
|
783
927
|
const displayName = message.sender === "agent" && senderName ? senderName : bubble[message.sender] ?? message.sender;
|
|
784
|
-
const tooltipText =
|
|
928
|
+
const tooltipText = displayStatus === "read" && message.readAt ? `${bubble.readAt}${formatDateTime(message.readAt)}` : displayStatus === "failed" ? bubble.windowExpired : void 0;
|
|
785
929
|
const tailCornerClass = isFirstInGroup ? isMine ? "rounded-tr-md" : "rounded-tl-md" : "";
|
|
786
930
|
const checkbox = /* @__PURE__ */ jsx10(
|
|
787
931
|
"button",
|
|
@@ -863,7 +1007,7 @@ function MessageBubble({
|
|
|
863
1007
|
] }),
|
|
864
1008
|
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-end gap-1 mt-0.5 select-none", children: [
|
|
865
1009
|
/* @__PURE__ */ jsx10("span", { className: "text-xs text-black/40 dark:text-white/40 font-medium", children: formatTimestamp(message.timestamp) }),
|
|
866
|
-
isMine &&
|
|
1010
|
+
isMine && displayStatus && /* @__PURE__ */ jsx10(StatusTicks, { status: displayStatus, title: tooltipText })
|
|
867
1011
|
] })
|
|
868
1012
|
]
|
|
869
1013
|
}
|
|
@@ -1129,7 +1273,7 @@ var EmojiPicker = ({ onSelect, labels, className = "" }) => {
|
|
|
1129
1273
|
};
|
|
1130
1274
|
|
|
1131
1275
|
// src/AudioRecorderButton.tsx
|
|
1132
|
-
import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState7 } from "react";
|
|
1276
|
+
import { useCallback as useCallback3, useEffect as useEffect2, useId, useRef as useRef2, useState as useState7 } from "react";
|
|
1133
1277
|
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1134
1278
|
var DEFAULT_AUDIO_RECORDER_BUTTON_LABELS = {
|
|
1135
1279
|
start: "Gravar \xE1udio",
|
|
@@ -1159,9 +1303,12 @@ function AudioRecorderButton({
|
|
|
1159
1303
|
reviewBeforeSend = true,
|
|
1160
1304
|
maxDurationMilliseconds = DEFAULT_MAX_RECORDING_MILLISECONDS,
|
|
1161
1305
|
labels,
|
|
1162
|
-
disabled
|
|
1306
|
+
disabled,
|
|
1307
|
+
disabledHint
|
|
1163
1308
|
}) {
|
|
1164
1309
|
const labelOf = (key) => labels?.[key] ?? DEFAULT_AUDIO_RECORDER_BUTTON_LABELS[key];
|
|
1310
|
+
const disabledHintId = useId();
|
|
1311
|
+
const showDisabledHint = Boolean(disabled && disabledHint);
|
|
1165
1312
|
const [isRecording, setIsRecording] = useState7(false);
|
|
1166
1313
|
const [pending, setPending] = useState7(void 0);
|
|
1167
1314
|
const recorderRef = useRef2(null);
|
|
@@ -1271,8 +1418,9 @@ function AudioRecorderButton({
|
|
|
1271
1418
|
type: "button",
|
|
1272
1419
|
disabled: disabled || pending !== void 0,
|
|
1273
1420
|
onClick: () => isRecording ? stop() : void start(),
|
|
1274
|
-
"data-cv-tooltip": toggleLabel,
|
|
1421
|
+
"data-cv-tooltip": showDisabledHint ? disabledHint : toggleLabel,
|
|
1275
1422
|
"aria-label": toggleLabel,
|
|
1423
|
+
"aria-describedby": showDisabledHint ? disabledHintId : void 0,
|
|
1276
1424
|
"aria-pressed": isRecording,
|
|
1277
1425
|
className: `flex h-10 w-10 items-center justify-center rounded-full transition-colors disabled:opacity-50 ${isRecording ? "animate-pulse bg-red-500 text-white ring-4 ring-red-500/30 hover:bg-red-600" : "text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700"}`,
|
|
1278
1426
|
children: isRecording ? /* @__PURE__ */ jsx13("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx13("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) }) : /* @__PURE__ */ jsxs9("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
@@ -1281,7 +1429,8 @@ function AudioRecorderButton({
|
|
|
1281
1429
|
/* @__PURE__ */ jsx13("line", { x1: "12", y1: "18", x2: "12", y2: "22" })
|
|
1282
1430
|
] })
|
|
1283
1431
|
}
|
|
1284
|
-
)
|
|
1432
|
+
),
|
|
1433
|
+
showDisabledHint && /* @__PURE__ */ jsx13("span", { id: disabledHintId, className: "sr-only", children: disabledHint })
|
|
1285
1434
|
] })
|
|
1286
1435
|
);
|
|
1287
1436
|
}
|
|
@@ -1292,7 +1441,7 @@ import {
|
|
|
1292
1441
|
useRef as useRef5,
|
|
1293
1442
|
useCallback as useCallback5,
|
|
1294
1443
|
useEffect as useEffect5,
|
|
1295
|
-
useId
|
|
1444
|
+
useId as useId2
|
|
1296
1445
|
} from "react";
|
|
1297
1446
|
|
|
1298
1447
|
// src/composer.constant.ts
|
|
@@ -1602,7 +1751,10 @@ var DEFAULT_MESSAGE_COMPOSER_LABELS = {
|
|
|
1602
1751
|
attach: "Anexar",
|
|
1603
1752
|
send: "Enviar",
|
|
1604
1753
|
removeAttachment: "Remover anexo",
|
|
1605
|
-
quickReplies: "Mensagens prontas"
|
|
1754
|
+
quickReplies: "Mensagens prontas",
|
|
1755
|
+
attachDisabledHint: "Este canal n\xE3o aceita anexo.",
|
|
1756
|
+
audioDisabledHint: "Este canal n\xE3o grava \xE1udio.",
|
|
1757
|
+
quickRepliesDisabledHint: "Este canal n\xE3o aceita mensagem pronta."
|
|
1606
1758
|
};
|
|
1607
1759
|
var DEFAULT_ACCEPTED_FILE_TYPES = [
|
|
1608
1760
|
"image/jpeg,image/png,image/webp",
|
|
@@ -1623,6 +1775,7 @@ var MessageComposer = ({
|
|
|
1623
1775
|
quickReplyVariables,
|
|
1624
1776
|
savedQuickReplies,
|
|
1625
1777
|
features,
|
|
1778
|
+
channel,
|
|
1626
1779
|
placeholder = "Digite uma mensagem...",
|
|
1627
1780
|
maxLength,
|
|
1628
1781
|
disabled = false,
|
|
@@ -1637,12 +1790,21 @@ var MessageComposer = ({
|
|
|
1637
1790
|
const sendLabel = labels?.send ?? DEFAULT_MESSAGE_COMPOSER_LABELS.send;
|
|
1638
1791
|
const removeAttachmentLabel = labels?.removeAttachment ?? DEFAULT_MESSAGE_COMPOSER_LABELS.removeAttachment;
|
|
1639
1792
|
const quickRepliesLabel = labels?.quickReplies ?? DEFAULT_MESSAGE_COMPOSER_LABELS.quickReplies;
|
|
1793
|
+
const attachDisabledHintLabel = labels?.attachDisabledHint ?? DEFAULT_MESSAGE_COMPOSER_LABELS.attachDisabledHint;
|
|
1794
|
+
const audioDisabledHintLabel = labels?.audioDisabledHint ?? DEFAULT_MESSAGE_COMPOSER_LABELS.audioDisabledHint;
|
|
1795
|
+
const quickRepliesDisabledHintLabel = labels?.quickRepliesDisabledHint ?? DEFAULT_MESSAGE_COMPOSER_LABELS.quickRepliesDisabledHint;
|
|
1796
|
+
const capability = channelCapabilityFor(channel);
|
|
1797
|
+
const attachmentsAllowed = capability.attachments.accepted;
|
|
1798
|
+
const audioRecordingAllowed = capability.audio.records;
|
|
1799
|
+
const quickRepliesAllowed = capability.quickReplies;
|
|
1800
|
+
const attachHintId = useId2();
|
|
1801
|
+
const quickRepliesHintId = useId2();
|
|
1640
1802
|
const [internalText, setInternalText] = useState9("");
|
|
1641
1803
|
const [showEmoji, setShowEmoji] = useState9(false);
|
|
1642
1804
|
const [attachments, setAttachments] = useState9([]);
|
|
1643
1805
|
const textareaRef = useRef5(null);
|
|
1644
1806
|
const fileInputRef = useRef5(null);
|
|
1645
|
-
const quickRepliesListboxId =
|
|
1807
|
+
const quickRepliesListboxId = useId2();
|
|
1646
1808
|
const [isQuickRepliesOpen, setIsQuickRepliesOpen] = useState9(false);
|
|
1647
1809
|
const [quickRepliesTerm, setQuickRepliesTerm] = useState9("");
|
|
1648
1810
|
const [shortcutStart, setShortcutStart] = useState9(null);
|
|
@@ -1823,7 +1985,14 @@ var MessageComposer = ({
|
|
|
1823
1985
|
return next;
|
|
1824
1986
|
});
|
|
1825
1987
|
}, []);
|
|
1826
|
-
const effectiveIdleAction = idleAction ?? (onAttach ? /* @__PURE__ */ jsx15(
|
|
1988
|
+
const effectiveIdleAction = idleAction ?? (onAttach ? /* @__PURE__ */ jsx15(
|
|
1989
|
+
AudioRecorderButton,
|
|
1990
|
+
{
|
|
1991
|
+
onRecorded: (file) => onAttach(file),
|
|
1992
|
+
disabled: !audioRecordingAllowed,
|
|
1993
|
+
disabledHint: audioRecordingAllowed ? void 0 : audioDisabledHintLabel
|
|
1994
|
+
}
|
|
1995
|
+
) : void 0);
|
|
1827
1996
|
const canSend = text.trim().length > 0 || attachments.length > 0;
|
|
1828
1997
|
const remaining = maxLength ? maxLength - text.length : null;
|
|
1829
1998
|
return (
|
|
@@ -1940,18 +2109,23 @@ var MessageComposer = ({
|
|
|
1940
2109
|
quickRepliesMode
|
|
1941
2110
|
) })
|
|
1942
2111
|
] }),
|
|
1943
|
-
showQuickRepliesButton && /* @__PURE__ */
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2112
|
+
showQuickRepliesButton && /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
2113
|
+
/* @__PURE__ */ jsx15(
|
|
2114
|
+
"button",
|
|
2115
|
+
{
|
|
2116
|
+
ref: quickRepliesTriggerRef,
|
|
2117
|
+
type: "button",
|
|
2118
|
+
disabled: !quickRepliesAllowed,
|
|
2119
|
+
"data-cv-tooltip": quickRepliesAllowed ? quickRepliesLabel : quickRepliesDisabledHintLabel,
|
|
2120
|
+
"aria-label": quickRepliesLabel,
|
|
2121
|
+
"aria-describedby": quickRepliesAllowed ? void 0 : quickRepliesHintId,
|
|
2122
|
+
onClick: openQuickRepliesViaButton,
|
|
2123
|
+
className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 flex-shrink-0 transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
2124
|
+
children: /* @__PURE__ */ jsx15("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx15("polygon", { points: "13 2 3 14 12 14 11 22 21 10 12 10 13 2" }) })
|
|
2125
|
+
}
|
|
2126
|
+
),
|
|
2127
|
+
!quickRepliesAllowed && /* @__PURE__ */ jsx15("span", { id: quickRepliesHintId, className: "sr-only", children: quickRepliesDisabledHintLabel })
|
|
2128
|
+
] }),
|
|
1955
2129
|
showAttachButton && /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
1956
2130
|
/* @__PURE__ */ jsx15(
|
|
1957
2131
|
"input",
|
|
@@ -1967,13 +2141,16 @@ var MessageComposer = ({
|
|
|
1967
2141
|
/* @__PURE__ */ jsx15(
|
|
1968
2142
|
"button",
|
|
1969
2143
|
{
|
|
1970
|
-
|
|
2144
|
+
disabled: !attachmentsAllowed,
|
|
2145
|
+
"data-cv-tooltip": attachmentsAllowed ? attachLabel : attachDisabledHintLabel,
|
|
1971
2146
|
onClick: () => fileInputRef.current?.click(),
|
|
1972
|
-
|
|
2147
|
+
"aria-describedby": attachmentsAllowed ? void 0 : attachHintId,
|
|
2148
|
+
className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 flex-shrink-0 transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
1973
2149
|
"aria-label": attachLabel,
|
|
1974
2150
|
children: /* @__PURE__ */ jsx15("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx15("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" }) })
|
|
1975
2151
|
}
|
|
1976
|
-
)
|
|
2152
|
+
),
|
|
2153
|
+
!attachmentsAllowed && /* @__PURE__ */ jsx15("span", { id: attachHintId, className: "sr-only", children: attachDisabledHintLabel })
|
|
1977
2154
|
] }),
|
|
1978
2155
|
!canSend && effectiveIdleAction ? /* @__PURE__ */ jsx15("div", { className: "flex-shrink-0", children: effectiveIdleAction }) : /* @__PURE__ */ jsx15(
|
|
1979
2156
|
"button",
|
|
@@ -2073,130 +2250,6 @@ function formatDividerLabel(iso, dateDivider) {
|
|
|
2073
2250
|
return date.toLocaleDateString("pt-BR", { day: "2-digit", month: "long", year: "numeric" });
|
|
2074
2251
|
}
|
|
2075
2252
|
|
|
2076
|
-
// src/lib/phone.ts
|
|
2077
|
-
function formatPhone(number) {
|
|
2078
|
-
const digits = number.replace(/\D/g, "");
|
|
2079
|
-
if (digits.length === 13) {
|
|
2080
|
-
return `+${digits.slice(0, 2)} (${digits.slice(2, 4)}) ${digits.slice(4, 9)}-${digits.slice(9, 13)}`;
|
|
2081
|
-
}
|
|
2082
|
-
if (digits.length === 12) {
|
|
2083
|
-
return `+${digits.slice(0, 2)} (${digits.slice(2, 4)}) ${digits.slice(4, 8)}-${digits.slice(8, 12)}`;
|
|
2084
|
-
}
|
|
2085
|
-
if (digits.length === 11) {
|
|
2086
|
-
return `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7, 11)}`;
|
|
2087
|
-
}
|
|
2088
|
-
if (digits.length === 10) {
|
|
2089
|
-
return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)}-${digits.slice(6, 10)}`;
|
|
2090
|
-
}
|
|
2091
|
-
return number;
|
|
2092
|
-
}
|
|
2093
|
-
var COUNTRY_FLAG_BY_DIAL_CODE = {
|
|
2094
|
-
"55": "\u{1F1E7}\u{1F1F7}",
|
|
2095
|
-
"351": "\u{1F1F5}\u{1F1F9}",
|
|
2096
|
-
"34": "\u{1F1EA}\u{1F1F8}",
|
|
2097
|
-
"54": "\u{1F1E6}\u{1F1F7}",
|
|
2098
|
-
"56": "\u{1F1E8}\u{1F1F1}",
|
|
2099
|
-
"57": "\u{1F1E8}\u{1F1F4}",
|
|
2100
|
-
"52": "\u{1F1F2}\u{1F1FD}",
|
|
2101
|
-
"598": "\u{1F1FA}\u{1F1FE}",
|
|
2102
|
-
"595": "\u{1F1F5}\u{1F1FE}",
|
|
2103
|
-
"44": "\u{1F1EC}\u{1F1E7}",
|
|
2104
|
-
"49": "\u{1F1E9}\u{1F1EA}",
|
|
2105
|
-
"39": "\u{1F1EE}\u{1F1F9}",
|
|
2106
|
-
"33": "\u{1F1EB}\u{1F1F7}"
|
|
2107
|
-
};
|
|
2108
|
-
function phoneCountryFlag(number) {
|
|
2109
|
-
const digits = number.replace(/\D/g, "");
|
|
2110
|
-
for (const length of [3, 2, 1]) {
|
|
2111
|
-
const flag = COUNTRY_FLAG_BY_DIAL_CODE[digits.slice(0, length)];
|
|
2112
|
-
if (flag) return flag;
|
|
2113
|
-
}
|
|
2114
|
-
return "";
|
|
2115
|
-
}
|
|
2116
|
-
function phoneInitials(number) {
|
|
2117
|
-
const digits = number.replace(/\D/g, "");
|
|
2118
|
-
return digits.slice(-2);
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
// src/conversationChannel.ts
|
|
2122
|
-
var CONVERSATION_CHANNEL = {
|
|
2123
|
-
WHATSAPP: "whatsapp",
|
|
2124
|
-
MESSENGER: "messenger",
|
|
2125
|
-
INSTAGRAM: "instagram",
|
|
2126
|
-
WEBCHAT: "webchat"
|
|
2127
|
-
};
|
|
2128
|
-
var DEFAULT_CONVERSATION_CHANNEL = CONVERSATION_CHANNEL.WHATSAPP;
|
|
2129
|
-
var REOPEN_MECHANISM = {
|
|
2130
|
-
TEMPLATE: "template",
|
|
2131
|
-
TAG: "tag",
|
|
2132
|
-
NONE: "none"
|
|
2133
|
-
};
|
|
2134
|
-
var HANDLE_KIND = {
|
|
2135
|
-
PHONE: "phone",
|
|
2136
|
-
USERNAME: "username",
|
|
2137
|
-
SESSION: "session"
|
|
2138
|
-
};
|
|
2139
|
-
var CHANNEL_CAPABILITIES = {
|
|
2140
|
-
[CONVERSATION_CHANNEL.WHATSAPP]: {
|
|
2141
|
-
label: "WhatsApp",
|
|
2142
|
-
icon: "\u{1F4AC}",
|
|
2143
|
-
hasSessionWindow: true,
|
|
2144
|
-
windowHours: 24,
|
|
2145
|
-
reopenMechanism: REOPEN_MECHANISM.TEMPLATE,
|
|
2146
|
-
handleKind: HANDLE_KIND.PHONE
|
|
2147
|
-
},
|
|
2148
|
-
[CONVERSATION_CHANNEL.MESSENGER]: {
|
|
2149
|
-
// Messenger também tem 24h, mas reabre com message tag — não com template aprovado.
|
|
2150
|
-
label: "Messenger",
|
|
2151
|
-
icon: "\u{1F4E8}",
|
|
2152
|
-
hasSessionWindow: true,
|
|
2153
|
-
windowHours: 24,
|
|
2154
|
-
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
2155
|
-
handleKind: HANDLE_KIND.USERNAME
|
|
2156
|
-
},
|
|
2157
|
-
[CONVERSATION_CHANNEL.INSTAGRAM]: {
|
|
2158
|
-
label: "Instagram",
|
|
2159
|
-
icon: "\u{1F4F7}",
|
|
2160
|
-
hasSessionWindow: true,
|
|
2161
|
-
windowHours: 24,
|
|
2162
|
-
reopenMechanism: REOPEN_MECHANISM.TAG,
|
|
2163
|
-
handleKind: HANDLE_KIND.USERNAME
|
|
2164
|
-
},
|
|
2165
|
-
[CONVERSATION_CHANNEL.WEBCHAT]: {
|
|
2166
|
-
// Chat próprio: sem intermediário, sem janela. Bloquear o composer aqui seria inventar limite.
|
|
2167
|
-
label: "Chat do site",
|
|
2168
|
-
icon: "\u{1F310}",
|
|
2169
|
-
hasSessionWindow: false,
|
|
2170
|
-
windowHours: 0,
|
|
2171
|
-
reopenMechanism: REOPEN_MECHANISM.NONE,
|
|
2172
|
-
handleKind: HANDLE_KIND.SESSION
|
|
2173
|
-
}
|
|
2174
|
-
};
|
|
2175
|
-
function capabilitiesOf(channel) {
|
|
2176
|
-
return CHANNEL_CAPABILITIES[channel ?? DEFAULT_CONVERSATION_CHANNEL];
|
|
2177
|
-
}
|
|
2178
|
-
var CHANNEL_FILTER_ALL = "all";
|
|
2179
|
-
function channelFiltersFor(conversations) {
|
|
2180
|
-
const present = new Set(
|
|
2181
|
-
conversations.map((conversation) => conversation.channel ?? DEFAULT_CONVERSATION_CHANNEL)
|
|
2182
|
-
);
|
|
2183
|
-
if (present.size < 2) return [];
|
|
2184
|
-
const ordered = Object.keys(CHANNEL_CAPABILITIES).filter((channel) => present.has(channel));
|
|
2185
|
-
return [
|
|
2186
|
-
{ value: CHANNEL_FILTER_ALL, label: "Todos" },
|
|
2187
|
-
...ordered.map((channel) => ({ value: channel, label: CHANNEL_CAPABILITIES[channel].label }))
|
|
2188
|
-
];
|
|
2189
|
-
}
|
|
2190
|
-
function formatContactHandle(params) {
|
|
2191
|
-
const { handleKind } = capabilitiesOf(params.channel);
|
|
2192
|
-
if (handleKind === HANDLE_KIND.PHONE) return formatPhone(params.handle);
|
|
2193
|
-
if (handleKind === HANDLE_KIND.USERNAME) return params.handle.startsWith("@") ? params.handle : `@${params.handle}`;
|
|
2194
|
-
return `Visitante ${params.handle.slice(-6)}`;
|
|
2195
|
-
}
|
|
2196
|
-
function contactFlag(params) {
|
|
2197
|
-
return capabilitiesOf(params.channel).handleKind === HANDLE_KIND.PHONE ? phoneCountryFlag(params.handle) : "";
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
2253
|
// src/hooks/useAsyncResource.ts
|
|
2201
2254
|
import { useCallback as useCallback6, useEffect as useEffect6, useRef as useRef6, useState as useState10 } from "react";
|
|
2202
2255
|
function useAsyncResource(fetcher, deps) {
|
|
@@ -3113,6 +3166,10 @@ var SIMULATOR_PANEL_CHANNEL_WORDING = {
|
|
|
3113
3166
|
[CONVERSATION_CHANNEL.WEBCHAT]: {
|
|
3114
3167
|
destinationHint: "entrega na API do chat do site",
|
|
3115
3168
|
placeholder: "Escreva como o visitante\u2026"
|
|
3169
|
+
},
|
|
3170
|
+
[CONVERSATION_CHANNEL.EMAIL]: {
|
|
3171
|
+
destinationHint: "entrega na caixa de entrada do contratante",
|
|
3172
|
+
placeholder: "Escreva como quem respondeu o e-mail\u2026"
|
|
3116
3173
|
}
|
|
3117
3174
|
};
|
|
3118
3175
|
function simulatorPanelLabelsOf(channel) {
|
|
@@ -3150,6 +3207,19 @@ function ConversationSimulatorPanel({
|
|
|
3150
3207
|
export {
|
|
3151
3208
|
ConversationLocalesProvider,
|
|
3152
3209
|
useConversationLocales,
|
|
3210
|
+
formatPhone,
|
|
3211
|
+
phoneInitials,
|
|
3212
|
+
CONVERSATION_CHANNEL,
|
|
3213
|
+
DEFAULT_CONVERSATION_CHANNEL,
|
|
3214
|
+
REOPEN_MECHANISM,
|
|
3215
|
+
HANDLE_KIND,
|
|
3216
|
+
CHANNEL_CAPABILITIES,
|
|
3217
|
+
capabilitiesOf,
|
|
3218
|
+
CHANNEL_FILTER_ALL,
|
|
3219
|
+
channelFiltersFor,
|
|
3220
|
+
formatContactHandle,
|
|
3221
|
+
contactFlag,
|
|
3222
|
+
channelCapabilityFor,
|
|
3153
3223
|
StatusTicks,
|
|
3154
3224
|
AudioPlayer,
|
|
3155
3225
|
AudioTranscription,
|
|
@@ -3197,18 +3267,6 @@ export {
|
|
|
3197
3267
|
DEFAULT_ACCEPTED_FILE_TYPES,
|
|
3198
3268
|
MessageComposer,
|
|
3199
3269
|
DateDivider,
|
|
3200
|
-
formatPhone,
|
|
3201
|
-
phoneInitials,
|
|
3202
|
-
CONVERSATION_CHANNEL,
|
|
3203
|
-
DEFAULT_CONVERSATION_CHANNEL,
|
|
3204
|
-
REOPEN_MECHANISM,
|
|
3205
|
-
HANDLE_KIND,
|
|
3206
|
-
CHANNEL_CAPABILITIES,
|
|
3207
|
-
capabilitiesOf,
|
|
3208
|
-
CHANNEL_FILTER_ALL,
|
|
3209
|
-
channelFiltersFor,
|
|
3210
|
-
formatContactHandle,
|
|
3211
|
-
contactFlag,
|
|
3212
3270
|
useAsyncResource,
|
|
3213
3271
|
conversationsOf,
|
|
3214
3272
|
totalOf,
|