@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.
@@ -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 = message.status === "read" && message.readAt ? `${bubble.readAt}${formatDateTime(message.readAt)}` : message.status === "failed" ? bubble.windowExpired : void 0;
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 && message.status && /* @__PURE__ */ jsx10(StatusTicks, { status: message.status, title: tooltipText })
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 = useId();
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(AudioRecorderButton, { onRecorded: (file) => onAttach(file) }) : void 0);
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__ */ jsx15(
1944
- "button",
1945
- {
1946
- ref: quickRepliesTriggerRef,
1947
- type: "button",
1948
- "data-cv-tooltip": quickRepliesLabel,
1949
- "aria-label": quickRepliesLabel,
1950
- onClick: openQuickRepliesViaButton,
1951
- className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 flex-shrink-0 transition-colors",
1952
- 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" }) })
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
- "data-cv-tooltip": attachLabel,
2144
+ disabled: !attachmentsAllowed,
2145
+ "data-cv-tooltip": attachmentsAllowed ? attachLabel : attachDisabledHintLabel,
1971
2146
  onClick: () => fileInputRef.current?.click(),
1972
- className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-500 hover:bg-gray-200 flex-shrink-0 transition-colors",
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,