@adatechnology/conversations-ui 0.1.0-rc.2 → 0.1.0-rc.4
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-4R6Y43DQ.js +726 -0
- package/dist/chunk-NV2RZ5KT.js +56 -0
- package/dist/{chunk-ZDURDZTM.js → chunk-OGRRHQQW.js} +1 -41
- package/dist/flows/index.js +6 -4
- package/dist/index.d.ts +323 -111
- package/dist/index.js +1032 -954
- package/dist/preview/index.d.ts +172 -0
- package/dist/preview/index.js +576 -0
- package/dist/styles.css +198 -0
- package/dist/types-C0PtaO7S.d.ts +207 -0
- package/package.json +10 -3
- package/src/Avatar.tsx +18 -3
- package/src/ChannelIcon.tsx +87 -0
- package/src/ConversationContextPanel.tsx +106 -0
- package/src/ConversationDocumentsPanel.tsx +107 -0
- package/src/ConversationHeader.tsx +239 -0
- package/src/ConversationListItem.tsx +36 -5
- package/src/ConversationLocalesProvider.tsx +16 -0
- package/src/ConversationRow.tsx +137 -0
- package/src/DateDivider.tsx +16 -3
- package/src/MediaRenderer.tsx +9 -9
- package/src/MessageBubble.tsx +24 -2
- package/src/MessageComposer.tsx +15 -2
- package/src/Wallpaper.tsx +4 -2
- package/src/WindowExpiredNotice.tsx +57 -0
- package/src/conversationChannel.test.ts +53 -0
- package/src/conversationChannel.ts +146 -0
- package/src/conversationTranscript.test.ts +65 -0
- package/src/conversationTranscript.ts +64 -0
- package/src/conversationWindow.test.ts +90 -0
- package/src/conversationWindow.ts +78 -0
- package/src/flows/FlowMapCanvas.tsx +2 -2
- package/src/hooks/useConversationDocuments.ts +4 -2
- package/src/index.ts +73 -4
- package/src/lib/cn.ts +15 -0
- package/src/lib/phone.ts +34 -0
- package/src/preview/ConversationPreview.tsx +148 -0
- package/src/preview/createMockConversationsApi.ts +111 -0
- package/src/preview/createMockSSEProvider.ts +40 -0
- package/src/preview/createPreviewWebhookClient.test.ts +105 -0
- package/src/preview/createPreviewWebhookClient.ts +99 -0
- package/src/preview/index.ts +40 -0
- package/src/preview/mockEventSource.ts +53 -0
- package/src/preview/preview.test.ts +175 -0
- package/src/preview/previewFixtures.ts +153 -0
- package/src/preview/previewStore.ts +193 -0
- package/src/preview/startPreviewScript.ts +60 -0
- package/src/providers/types.ts +36 -2
- package/src/settings/WhatsAppTemplatesSettings.tsx +106 -0
- package/src/styles.css +136 -0
- package/src/types.ts +8 -0
- package/src/useDarkMode.ts +26 -0
- package/src/useIsNarrow.ts +29 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/useDarkMode.ts
|
|
2
|
+
import { useState, useEffect, useCallback } from "react";
|
|
3
|
+
var STORAGE_KEY = "conversations-ui-dark-mode";
|
|
4
|
+
function getInitialDark() {
|
|
5
|
+
if (typeof window === "undefined") return false;
|
|
6
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
7
|
+
if (stored !== null) {
|
|
8
|
+
return stored === "true";
|
|
9
|
+
}
|
|
10
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
11
|
+
}
|
|
12
|
+
function useIsDarkTheme() {
|
|
13
|
+
const [isDark, setIsDark] = useState(
|
|
14
|
+
() => typeof document !== "undefined" && document.documentElement.classList.contains("dark")
|
|
15
|
+
);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const root = document.documentElement;
|
|
18
|
+
const observer = new MutationObserver(() => setIsDark(root.classList.contains("dark")));
|
|
19
|
+
observer.observe(root, { attributes: true, attributeFilter: ["class"] });
|
|
20
|
+
setIsDark(root.classList.contains("dark"));
|
|
21
|
+
return () => observer.disconnect();
|
|
22
|
+
}, []);
|
|
23
|
+
return isDark;
|
|
24
|
+
}
|
|
25
|
+
function useDarkMode() {
|
|
26
|
+
const [isDark, setIsDark] = useState(getInitialDark);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const root = document.documentElement;
|
|
29
|
+
if (isDark) {
|
|
30
|
+
root.classList.add("dark");
|
|
31
|
+
} else {
|
|
32
|
+
root.classList.remove("dark");
|
|
33
|
+
}
|
|
34
|
+
localStorage.setItem(STORAGE_KEY, String(isDark));
|
|
35
|
+
}, [isDark]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
38
|
+
const handleChange = (event) => {
|
|
39
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
40
|
+
if (stored === null) {
|
|
41
|
+
setIsDark(event.matches);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
45
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
46
|
+
}, []);
|
|
47
|
+
const toggle = useCallback(() => {
|
|
48
|
+
setIsDark((prev) => !prev);
|
|
49
|
+
}, []);
|
|
50
|
+
return { isDark, toggle };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
useIsDarkTheme,
|
|
55
|
+
useDarkMode
|
|
56
|
+
};
|
|
@@ -151,49 +151,9 @@ function waToHTMLInline(text) {
|
|
|
151
151
|
return escapeHtml(text).replace(/\*([^*\n]+)\*/g, "<strong>$1</strong>").replace(/_([^_\n]+)_/g, "<em>$1</em>").replace(/~([^~\n]+)~/g, "<del>$1</del>").replace(/`([^`\n]+)`/g, "<code>$1</code>");
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
// src/useDarkMode.ts
|
|
155
|
-
import { useState, useEffect, useCallback } from "react";
|
|
156
|
-
var STORAGE_KEY = "conversations-ui-dark-mode";
|
|
157
|
-
function getInitialDark() {
|
|
158
|
-
if (typeof window === "undefined") return false;
|
|
159
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
160
|
-
if (stored !== null) {
|
|
161
|
-
return stored === "true";
|
|
162
|
-
}
|
|
163
|
-
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
164
|
-
}
|
|
165
|
-
function useDarkMode() {
|
|
166
|
-
const [isDark, setIsDark] = useState(getInitialDark);
|
|
167
|
-
useEffect(() => {
|
|
168
|
-
const root = document.documentElement;
|
|
169
|
-
if (isDark) {
|
|
170
|
-
root.classList.add("dark");
|
|
171
|
-
} else {
|
|
172
|
-
root.classList.remove("dark");
|
|
173
|
-
}
|
|
174
|
-
localStorage.setItem(STORAGE_KEY, String(isDark));
|
|
175
|
-
}, [isDark]);
|
|
176
|
-
useEffect(() => {
|
|
177
|
-
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
178
|
-
const handleChange = (event) => {
|
|
179
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
180
|
-
if (stored === null) {
|
|
181
|
-
setIsDark(event.matches);
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
mediaQuery.addEventListener("change", handleChange);
|
|
185
|
-
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
186
|
-
}, []);
|
|
187
|
-
const toggle = useCallback(() => {
|
|
188
|
-
setIsDark((prev) => !prev);
|
|
189
|
-
}, []);
|
|
190
|
-
return { isDark, toggle };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
154
|
export {
|
|
194
155
|
parseWhatsAppFormatting,
|
|
195
156
|
waToHTML,
|
|
196
157
|
htmlToWA,
|
|
197
|
-
waToHTMLInline
|
|
198
|
-
useDarkMode
|
|
158
|
+
waToHTMLInline
|
|
199
159
|
};
|
package/dist/flows/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
useIsDarkTheme
|
|
3
|
+
} from "../chunk-NV2RZ5KT.js";
|
|
4
|
+
import {
|
|
5
|
+
parseWhatsAppFormatting
|
|
6
|
+
} from "../chunk-OGRRHQQW.js";
|
|
5
7
|
|
|
6
8
|
// src/flows/FlowNodeCard.tsx
|
|
7
9
|
import { Handle, Position } from "@xyflow/react";
|
|
@@ -524,7 +526,7 @@ var BACKGROUND_COLOR_LIGHT = "#cbd5e1";
|
|
|
524
526
|
var BACKGROUND_COLOR_DARK = "#334155";
|
|
525
527
|
function FlowMapCanvas({ graphs, rootKey, onOpenFlow, labels: labelsOverride }) {
|
|
526
528
|
const labels = { ...DEFAULT_FLOW_EDITOR_LABELS, ...labelsOverride };
|
|
527
|
-
const
|
|
529
|
+
const isDark = useIsDarkTheme();
|
|
528
530
|
const positions = useMemo(() => computeFlowMapLayout(graphs, rootKey), [graphs, rootKey]);
|
|
529
531
|
const nodes = useMemo(
|
|
530
532
|
() => Object.values(graphs).map((g) => ({
|
package/dist/index.d.ts
CHANGED
|
@@ -1,47 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import react__default, { ReactNode, FormEvent } from 'react';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
apiBaseUrl: string;
|
|
6
|
-
theme?: ConversationsTheme;
|
|
7
|
-
features?: ConversationsFeatures;
|
|
8
|
-
}
|
|
9
|
-
interface ConversationsTheme {
|
|
10
|
-
primaryColor?: string;
|
|
11
|
-
backgroundColor?: string;
|
|
12
|
-
bubbleSent?: string;
|
|
13
|
-
bubbleReceived?: string;
|
|
14
|
-
textPrimary?: string;
|
|
15
|
-
textSecondary?: string;
|
|
16
|
-
}
|
|
17
|
-
interface ConversationsFeatures {
|
|
18
|
-
audio?: boolean;
|
|
19
|
-
documents?: boolean;
|
|
20
|
-
emoji?: boolean;
|
|
21
|
-
darkMode?: boolean;
|
|
22
|
-
}
|
|
23
|
-
interface MessagePayload {
|
|
24
|
-
id: string;
|
|
25
|
-
type: 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'template';
|
|
26
|
-
content?: string;
|
|
27
|
-
caption?: string;
|
|
28
|
-
mediaUrl?: string;
|
|
29
|
-
base64?: string;
|
|
30
|
-
uploadId?: string;
|
|
31
|
-
mediaId?: string;
|
|
32
|
-
mimeType?: string;
|
|
33
|
-
filename?: string;
|
|
34
|
-
sizeBytes?: number;
|
|
35
|
-
direction: 'inbound' | 'outbound';
|
|
36
|
-
sender: 'bot' | 'customer' | 'agent';
|
|
37
|
-
timestamp: string;
|
|
38
|
-
status?: 'sent' | 'delivered' | 'read' | 'failed';
|
|
39
|
-
readAt?: string;
|
|
40
|
-
agentName?: string | null;
|
|
41
|
-
templateName?: string;
|
|
42
|
-
isFirstInGroup?: boolean;
|
|
43
|
-
isLastInGroup?: boolean;
|
|
44
|
-
}
|
|
3
|
+
import { M as MessagePayload, k as ConversationsFeatures, i as ConversationSummary, f as ConversationChannel, j as ConversationsApi, S as SSEProvider, g as ConversationDocument } from './types-C0PtaO7S.js';
|
|
4
|
+
export { C as CHANNEL_CAPABILITIES, a as CHANNEL_FILTER_ALL, b as CONVERSATION_CHANNEL, c as ChannelCapabilities, d as ChannelFilter, e as ChannelFilterOption, h as ConversationEventSource, l as ConversationsTheme, m as ConversationsUIConfig, D as DEFAULT_CONVERSATION_CHANNEL, F as FormatContactHandleParams, H as HANDLE_KIND, n as HandleKind, R as REOPEN_MECHANISM, o as ReopenMechanism, p as capabilitiesOf, q as channelFiltersFor, r as contactFlag, s as formatContactHandle } from './types-C0PtaO7S.js';
|
|
45
5
|
|
|
46
6
|
type ResolveMediaUrl = (message: MessagePayload) => Promise<string | null>;
|
|
47
7
|
interface MediaRendererProps {
|
|
@@ -60,8 +20,9 @@ interface MessageBubbleProps {
|
|
|
60
20
|
isSelected?: boolean;
|
|
61
21
|
onToggleSelect?: () => void;
|
|
62
22
|
onResolveMediaUrl?: ResolveMediaUrl;
|
|
23
|
+
className?: string;
|
|
63
24
|
}
|
|
64
|
-
declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, }: MessageBubbleProps): react.JSX.Element;
|
|
25
|
+
declare function MessageBubble({ message, isMine, senderName, isFirstInGroup, isSelecting, isSelected, onToggleSelect, onResolveMediaUrl, className, }: MessageBubbleProps): react.JSX.Element;
|
|
65
26
|
|
|
66
27
|
interface ConversationWallpaperProps {
|
|
67
28
|
children?: ReactNode;
|
|
@@ -82,6 +43,14 @@ interface ConversationLocales {
|
|
|
82
43
|
viewImage: string;
|
|
83
44
|
listenAudio: string;
|
|
84
45
|
viewVideo: string;
|
|
46
|
+
moderationFlagged: string;
|
|
47
|
+
mediaLoading: string;
|
|
48
|
+
mediaRetry: string;
|
|
49
|
+
mediaError: string;
|
|
50
|
+
mediaUnavailable: string;
|
|
51
|
+
imageAlt: string;
|
|
52
|
+
untitledDocument: string;
|
|
53
|
+
downloadFile: string;
|
|
85
54
|
};
|
|
86
55
|
selection: {
|
|
87
56
|
select: string;
|
|
@@ -123,8 +92,14 @@ interface MessageComposerProps {
|
|
|
123
92
|
maxLength?: number;
|
|
124
93
|
disabled?: boolean;
|
|
125
94
|
acceptedFileTypes?: string;
|
|
95
|
+
className?: string;
|
|
96
|
+
classNames?: Partial<MessageComposerClassNames>;
|
|
97
|
+
}
|
|
98
|
+
interface MessageComposerClassNames {
|
|
99
|
+
root: string;
|
|
100
|
+
field: string;
|
|
126
101
|
}
|
|
127
|
-
declare const MessageComposer: ({ onSend, onAttach, value: externalValue, onChange: externalOnChange, features, placeholder, maxLength, disabled, acceptedFileTypes, }: MessageComposerProps) => react.JSX.Element;
|
|
102
|
+
declare const MessageComposer: ({ onSend, onAttach, value: externalValue, onChange: externalOnChange, features, placeholder, maxLength, disabled, acceptedFileTypes, className, classNames, }: MessageComposerProps) => react.JSX.Element;
|
|
128
103
|
|
|
129
104
|
interface WhatsAppMessageEditorProps {
|
|
130
105
|
value: string;
|
|
@@ -145,10 +120,16 @@ interface SimpleEmojiPickerProps {
|
|
|
145
120
|
}
|
|
146
121
|
declare function SimpleEmojiPicker({ onSelect, label, pickerWidth, pickerMaxHeight }: SimpleEmojiPickerProps): react.JSX.Element;
|
|
147
122
|
|
|
123
|
+
interface DateDividerClassNames {
|
|
124
|
+
root: string;
|
|
125
|
+
label: string;
|
|
126
|
+
}
|
|
148
127
|
interface DateDividerProps {
|
|
149
128
|
iso: string;
|
|
129
|
+
className?: string;
|
|
130
|
+
classNames?: Partial<DateDividerClassNames>;
|
|
150
131
|
}
|
|
151
|
-
declare function DateDivider({ iso }: DateDividerProps): react.JSX.Element;
|
|
132
|
+
declare function DateDivider({ iso, className, classNames }: DateDividerProps): react.JSX.Element;
|
|
152
133
|
|
|
153
134
|
interface AvatarProps {
|
|
154
135
|
name?: string | null;
|
|
@@ -158,76 +139,25 @@ interface AvatarProps {
|
|
|
158
139
|
}
|
|
159
140
|
declare function Avatar({ name, avatarUrl, size, className }: AvatarProps): react.JSX.Element;
|
|
160
141
|
|
|
161
|
-
interface ConversationsApi {
|
|
162
|
-
fetchMessages(conversationId: string, params?: {
|
|
163
|
-
limit?: number;
|
|
164
|
-
before?: string;
|
|
165
|
-
}): Promise<MessagePayload[]>;
|
|
166
|
-
fetchConversations(params?: {
|
|
167
|
-
page?: number;
|
|
168
|
-
limit?: number;
|
|
169
|
-
waitingHuman?: boolean;
|
|
170
|
-
search?: string;
|
|
171
|
-
}): Promise<ConversationSummary[]>;
|
|
172
|
-
sendMessage(conversationId: string, text: string): Promise<MessagePayload>;
|
|
173
|
-
sendMedia(conversationId: string, data: {
|
|
174
|
-
base64: string;
|
|
175
|
-
mimeType: string;
|
|
176
|
-
filename: string;
|
|
177
|
-
caption?: string;
|
|
178
|
-
}): Promise<MessagePayload>;
|
|
179
|
-
sendTemplate(conversationId: string, data: {
|
|
180
|
-
templateName: string;
|
|
181
|
-
languageCode?: string;
|
|
182
|
-
bodyParams?: string[];
|
|
183
|
-
}): Promise<void>;
|
|
184
|
-
markRead(conversationId: string): Promise<void>;
|
|
185
|
-
getContext(conversationId: string): Promise<Record<string, unknown>>;
|
|
186
|
-
getDocuments(conversationId: string, params?: {
|
|
187
|
-
search?: string;
|
|
188
|
-
page?: number;
|
|
189
|
-
}): Promise<ConversationDocument[]>;
|
|
190
|
-
getDocumentUrl(uploadId: string): Promise<string>;
|
|
191
|
-
getMediaProxyUrl(mediaId: string): Promise<{
|
|
192
|
-
mimeType: string;
|
|
193
|
-
data: string;
|
|
194
|
-
}>;
|
|
195
|
-
}
|
|
196
|
-
interface SSEProvider {
|
|
197
|
-
connectConversationStream(conversationId: string): EventSource;
|
|
198
|
-
connectGlobalStream(): EventSource;
|
|
199
|
-
}
|
|
200
|
-
interface ConversationSummary {
|
|
201
|
-
id: string;
|
|
202
|
-
whatsappNumber: string;
|
|
203
|
-
clientName?: string;
|
|
204
|
-
lastContent?: string;
|
|
205
|
-
lastDirection?: 'inbound' | 'outbound';
|
|
206
|
-
lastAt: string;
|
|
207
|
-
lastInboundAt: string | null;
|
|
208
|
-
mode: 'bot' | 'human';
|
|
209
|
-
assignedUserId: string | null;
|
|
210
|
-
waitingHuman: boolean;
|
|
211
|
-
unread: number;
|
|
212
|
-
currentState: string;
|
|
213
|
-
}
|
|
214
|
-
interface ConversationDocument {
|
|
215
|
-
id: string;
|
|
216
|
-
filename: string;
|
|
217
|
-
mimeType: string;
|
|
218
|
-
sizeBytes: number;
|
|
219
|
-
source: string;
|
|
220
|
-
linkedAt: string;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
142
|
interface ConversationListItemProps {
|
|
224
143
|
conversation: ConversationSummary;
|
|
225
144
|
active?: boolean;
|
|
226
145
|
selected?: boolean;
|
|
227
146
|
onClick?: () => void;
|
|
228
147
|
onSelect?: (id: string) => void;
|
|
229
|
-
|
|
230
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Desliga a borda inferior quando o item é composto dentro de outra linha (ver `ConversationRow`):
|
|
150
|
+
* com ela ligada, a borda corta a própria linha ao meio, separando o item do rodapé de status.
|
|
151
|
+
*/
|
|
152
|
+
showDivider?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Desliga o fundo de selecionado. Par do `showDivider`: quando o item é composto dentro de uma
|
|
155
|
+
* linha maior, quem pinta o realce é a linha — senão só o bloco do item fica cinza e o resto
|
|
156
|
+
* (checkbox, pills, barra lateral) continua branco, como se metade da linha estivesse selecionada.
|
|
157
|
+
*/
|
|
158
|
+
highlightActive?: boolean;
|
|
159
|
+
}
|
|
160
|
+
declare const ConversationListItem: ({ conversation, active, selected, onClick, onSelect, showDivider, highlightActive, }: ConversationListItemProps) => react.JSX.Element;
|
|
231
161
|
|
|
232
162
|
type ToastType = 'success' | 'error' | 'info';
|
|
233
163
|
interface ToastContextValue {
|
|
@@ -279,11 +209,258 @@ interface MessageTailProps {
|
|
|
279
209
|
}
|
|
280
210
|
declare function MessageTail({ isOutbound }: MessageTailProps): react.JSX.Element;
|
|
281
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Janela de sessão: o intervalo em que o canal aceita mensagem livre do atendente. No WhatsApp são
|
|
214
|
+
* 24h desde o último contato do cliente; fora dela só template. Cada canal tem a sua regra — e há
|
|
215
|
+
* canal sem janela nenhuma — então a política vem de `capabilitiesOf`, não de constante fixa.
|
|
216
|
+
*
|
|
217
|
+
* Mora no SDK porque é regra de plataforma, não de produto: todo projeto que usa este pacote
|
|
218
|
+
* precisa dela para saber o que o atendente ainda consegue fazer.
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
declare const CONVERSATION_WINDOW: {
|
|
222
|
+
readonly ALL: "all";
|
|
223
|
+
readonly FRESH: "fresh";
|
|
224
|
+
readonly WARNING: "warning";
|
|
225
|
+
readonly CRITICAL: "critical";
|
|
226
|
+
readonly EXPIRED: "expired";
|
|
227
|
+
};
|
|
228
|
+
type ConversationWindow = (typeof CONVERSATION_WINDOW)[keyof typeof CONVERSATION_WINDOW];
|
|
229
|
+
declare const WINDOW_FILTERS: readonly [{
|
|
230
|
+
readonly value: "all";
|
|
231
|
+
readonly label: "Todas";
|
|
232
|
+
readonly dotClass: "";
|
|
233
|
+
}, {
|
|
234
|
+
readonly value: "fresh";
|
|
235
|
+
readonly label: "<12h";
|
|
236
|
+
readonly dotClass: "bg-green-500";
|
|
237
|
+
}, {
|
|
238
|
+
readonly value: "warning";
|
|
239
|
+
readonly label: "12-21h";
|
|
240
|
+
readonly dotClass: "bg-yellow-500";
|
|
241
|
+
}, {
|
|
242
|
+
readonly value: "critical";
|
|
243
|
+
readonly label: "21-24h";
|
|
244
|
+
readonly dotClass: "bg-red-500";
|
|
245
|
+
}, {
|
|
246
|
+
readonly value: "expired";
|
|
247
|
+
readonly label: ">24h";
|
|
248
|
+
readonly dotClass: "bg-gray-400";
|
|
249
|
+
}];
|
|
250
|
+
type WindowOfParams = {
|
|
251
|
+
readonly lastInboundAt: string | null;
|
|
252
|
+
readonly now: number;
|
|
253
|
+
/** Ausente = `whatsapp`. */
|
|
254
|
+
readonly channel?: ConversationChannel | undefined;
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Sem `lastInboundAt` o cliente nunca escreveu, então não há janela aberta — classificar como
|
|
258
|
+
* expirada é o comportamento seguro: evita o atendente tentar texto livre e receber recusa.
|
|
259
|
+
*
|
|
260
|
+
* Canal sem janela de sessão (chat de site) é sempre `fresh`: ali nada expira, e marcar expirado
|
|
261
|
+
* bloquearia o composer inventando um limite que a plataforma não impõe.
|
|
262
|
+
*/
|
|
263
|
+
declare function windowOf(params: WindowOfParams): ConversationWindow;
|
|
264
|
+
declare function formatStalledFor(lastAt: string, now: number): string;
|
|
265
|
+
|
|
266
|
+
type ConversationRowClassNames = {
|
|
267
|
+
root: string;
|
|
268
|
+
windowBar: string;
|
|
269
|
+
};
|
|
270
|
+
type ConversationRowProps = {
|
|
271
|
+
conversation: ConversationSummary;
|
|
272
|
+
active: boolean;
|
|
273
|
+
selected: boolean;
|
|
274
|
+
now: number;
|
|
275
|
+
busy: boolean;
|
|
276
|
+
onOpen: () => void;
|
|
277
|
+
onToggleSelected: () => void;
|
|
278
|
+
onTakeover: () => void;
|
|
279
|
+
className?: string;
|
|
280
|
+
classNames?: Partial<ConversationRowClassNames>;
|
|
281
|
+
};
|
|
282
|
+
declare function ConversationRow({ conversation, active, selected, now, busy, onOpen, onToggleSelected, onTakeover, className, classNames, }: ConversationRowProps): react.JSX.Element;
|
|
283
|
+
|
|
284
|
+
declare const CHANNEL_BRAND_COLOR: Readonly<Record<ConversationChannel, string>>;
|
|
285
|
+
interface ChannelIconProps {
|
|
286
|
+
channel?: ConversationChannel | undefined;
|
|
287
|
+
size?: number;
|
|
288
|
+
className?: string;
|
|
289
|
+
}
|
|
290
|
+
declare function ChannelIcon({ channel, size, className }: ChannelIconProps): react.JSX.Element;
|
|
291
|
+
|
|
292
|
+
interface ConversationHeaderLabels {
|
|
293
|
+
botMode: string;
|
|
294
|
+
humanMode: string;
|
|
295
|
+
returnToBot: string;
|
|
296
|
+
finish: string;
|
|
297
|
+
takeover: string;
|
|
298
|
+
download: string;
|
|
299
|
+
documents: string;
|
|
300
|
+
back: string;
|
|
301
|
+
moreActions: string;
|
|
302
|
+
}
|
|
303
|
+
declare const DEFAULT_CONVERSATION_HEADER_LABELS: ConversationHeaderLabels;
|
|
304
|
+
/**
|
|
305
|
+
* Partes estilizáveis do cabeçalho. Cada chave recebe classes que o `cn` funde por cima da base, e
|
|
306
|
+
* conflito de utilitário (padding, gap, borda) fica com o valor do produto.
|
|
307
|
+
*/
|
|
308
|
+
interface ConversationHeaderClassNames {
|
|
309
|
+
root: string;
|
|
310
|
+
identity: string;
|
|
311
|
+
name: string;
|
|
312
|
+
meta: string;
|
|
313
|
+
actions: string;
|
|
314
|
+
desktopActions: string;
|
|
315
|
+
mobileMenu: string;
|
|
316
|
+
}
|
|
317
|
+
interface ConversationHeaderProps {
|
|
318
|
+
conversation: ConversationSummary;
|
|
319
|
+
busy?: boolean;
|
|
320
|
+
onTakeover?: () => void;
|
|
321
|
+
onReturnToBot?: () => void;
|
|
322
|
+
onFinish?: () => void;
|
|
323
|
+
onDownload?: () => void;
|
|
324
|
+
onOpenDocuments?: () => void;
|
|
325
|
+
documentsOpen?: boolean;
|
|
326
|
+
onBack?: () => void;
|
|
327
|
+
labels?: Partial<ConversationHeaderLabels>;
|
|
328
|
+
className?: string;
|
|
329
|
+
classNames?: Partial<ConversationHeaderClassNames>;
|
|
330
|
+
}
|
|
331
|
+
declare function ConversationHeader({ conversation, busy, onTakeover, onReturnToBot, onFinish, onDownload, onOpenDocuments, documentsOpen, onBack, labels: labelsOverride, className, classNames, }: ConversationHeaderProps): react.JSX.Element;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* "Suas Seleções": o que o bot já coletou na conversa. Para o atendente que assume no meio, é a
|
|
335
|
+
* diferença entre ler o transcript inteiro e ver o estado em duas linhas.
|
|
336
|
+
*
|
|
337
|
+
* O pacote não interpreta o contexto — ele é `Record<string, unknown>` e cada produto nomeia as
|
|
338
|
+
* próprias chaves. O host traduz para `entries`; aqui só se decide como mostrar.
|
|
339
|
+
*/
|
|
340
|
+
interface ConversationContextEntry {
|
|
341
|
+
key: string;
|
|
342
|
+
label: string;
|
|
343
|
+
value?: string | undefined;
|
|
344
|
+
icon?: string;
|
|
345
|
+
}
|
|
346
|
+
interface ConversationContextPanelLabels {
|
|
347
|
+
title: string;
|
|
348
|
+
empty: string;
|
|
349
|
+
}
|
|
350
|
+
declare const DEFAULT_CONVERSATION_CONTEXT_LABELS: ConversationContextPanelLabels;
|
|
351
|
+
interface ConversationContextPanelClassNames {
|
|
352
|
+
root: string;
|
|
353
|
+
toggle: string;
|
|
354
|
+
counter: string;
|
|
355
|
+
body: string;
|
|
356
|
+
}
|
|
357
|
+
interface ConversationContextPanelProps {
|
|
358
|
+
entries: readonly ConversationContextEntry[];
|
|
359
|
+
labels?: Partial<ConversationContextPanelLabels>;
|
|
360
|
+
className?: string;
|
|
361
|
+
classNames?: Partial<ConversationContextPanelClassNames>;
|
|
362
|
+
}
|
|
363
|
+
declare function ConversationContextPanel({ entries, labels: labelsOverride, className, classNames, }: ConversationContextPanelProps): react.JSX.Element;
|
|
364
|
+
|
|
365
|
+
interface WindowExpiredNoticeLabels {
|
|
366
|
+
title: string;
|
|
367
|
+
description: string;
|
|
368
|
+
sendTemplate: string;
|
|
369
|
+
}
|
|
370
|
+
declare const DEFAULT_WINDOW_EXPIRED_LABELS: WindowExpiredNoticeLabels;
|
|
371
|
+
interface WindowExpiredNoticeProps {
|
|
372
|
+
onSendTemplate?: () => void;
|
|
373
|
+
disabled?: boolean;
|
|
374
|
+
labels?: Partial<WindowExpiredNoticeLabels>;
|
|
375
|
+
className?: string;
|
|
376
|
+
}
|
|
377
|
+
declare function WindowExpiredNotice({ onSendTemplate, disabled, labels: labelsOverride, className, }: WindowExpiredNoticeProps): react.JSX.Element;
|
|
378
|
+
/**
|
|
379
|
+
* Só a faixa `expired` bloqueia: 21-24h ainda aceita texto livre e merece alerta, não impedimento.
|
|
380
|
+
*/
|
|
381
|
+
declare function isWindowBlocking(window: ConversationWindow): boolean;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Arquivos trocados na conversa. Sai do transcript e vira lista própria porque anexo é o que o
|
|
385
|
+
* atendente mais precisa reencontrar depois — rolar meses de mensagens para achar um comprovante é
|
|
386
|
+
* o caso que a busca por documento existe para eliminar.
|
|
387
|
+
*
|
|
388
|
+
* Usa `useConversationDocuments`, então funciona com qualquer `ConversationsApi`. Host sem
|
|
389
|
+
* biblioteca de documentos cai no estado vazio, sem quebrar.
|
|
390
|
+
*/
|
|
391
|
+
interface ConversationDocumentsPanelLabels {
|
|
392
|
+
toggle: string;
|
|
393
|
+
title: string;
|
|
394
|
+
searchPlaceholder: string;
|
|
395
|
+
empty: string;
|
|
396
|
+
loading: string;
|
|
397
|
+
failure: string;
|
|
398
|
+
download: string;
|
|
399
|
+
}
|
|
400
|
+
declare const DEFAULT_CONVERSATION_DOCUMENTS_LABELS: ConversationDocumentsPanelLabels;
|
|
401
|
+
interface ConversationDocumentsPanelClassNames {
|
|
402
|
+
root: string;
|
|
403
|
+
body: string;
|
|
404
|
+
}
|
|
405
|
+
interface ConversationDocumentsPanelProps {
|
|
406
|
+
conversationId: string;
|
|
407
|
+
/** Controlado de fora porque o gatilho vive no cabeçalho, junto das outras ações da conversa. */
|
|
408
|
+
open: boolean;
|
|
409
|
+
labels?: Partial<ConversationDocumentsPanelLabels>;
|
|
410
|
+
className?: string;
|
|
411
|
+
classNames?: Partial<ConversationDocumentsPanelClassNames>;
|
|
412
|
+
}
|
|
413
|
+
declare function ConversationDocumentsPanel({ conversationId, open, labels: labelsOverride, className, classNames, }: ConversationDocumentsPanelProps): react.JSX.Element | null;
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Serialização do transcript para download. Fica no pacote porque o formato de um histórico de
|
|
417
|
+
* WhatsApp legível não é regra de negócio de ninguém — e porque o host que já tem as mensagens em
|
|
418
|
+
* tela não deveria precisar de rota nova só para salvar um arquivo.
|
|
419
|
+
*
|
|
420
|
+
* Funções puras, separadas do disparo do download: é o que permite testá-las sem DOM.
|
|
421
|
+
*/
|
|
422
|
+
|
|
423
|
+
type BuildTranscriptTextParams = {
|
|
424
|
+
readonly messages: readonly MessagePayload[];
|
|
425
|
+
readonly whatsappNumber: string;
|
|
426
|
+
readonly clientName?: string | undefined;
|
|
427
|
+
};
|
|
428
|
+
/**
|
|
429
|
+
* Formato próximo ao export nativo do WhatsApp (`[data hora] Autor: texto`), que é o que pessoas e
|
|
430
|
+
* ferramentas de suporte já sabem ler.
|
|
431
|
+
*/
|
|
432
|
+
declare function buildTranscriptText(params: BuildTranscriptTextParams): string;
|
|
433
|
+
declare function buildTranscriptFilename(whatsappNumber: string, generatedAt: Date): string;
|
|
434
|
+
/**
|
|
435
|
+
* Dispara o download no navegador. `revokeObjectURL` no fim não é higiene opcional: sem ele cada
|
|
436
|
+
* export retém o blob inteiro em memória até a aba fechar.
|
|
437
|
+
*/
|
|
438
|
+
declare function downloadTextFile(filename: string, content: string): void;
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Leitura passiva do tema do host: observa a classe `dark` no `<html>` e não escreve nada.
|
|
442
|
+
*
|
|
443
|
+
* Existe porque `useDarkMode` é um controlador — ele grava a classe e persiste a preferência. Um
|
|
444
|
+
* componente que só precisa escolher cor não pode usá-lo: bastava renderizar o mapa de fluxos
|
|
445
|
+
* para o app inteiro do host trocar de tema, seguindo o `prefers-color-scheme` do sistema em vez
|
|
446
|
+
* da configuração da aplicação.
|
|
447
|
+
*/
|
|
448
|
+
declare function useIsDarkTheme(): boolean;
|
|
282
449
|
declare function useDarkMode(): {
|
|
283
450
|
isDark: boolean;
|
|
284
451
|
toggle: () => void;
|
|
285
452
|
};
|
|
286
453
|
|
|
454
|
+
/**
|
|
455
|
+
* Detecta tela estreita para decisões que CSS não resolve — como abrir ou não um painel por padrão.
|
|
456
|
+
*
|
|
457
|
+
* O breakpoint é o mesmo das classes `cv-only-*` e `.cv-back` (1024px). Duplicado aqui porque
|
|
458
|
+
* JavaScript não lê media query do stylesheet; se um dia divergirem, o sintoma é painel abrindo
|
|
459
|
+
* numa largura onde o resto da UI já mudou de modo.
|
|
460
|
+
*/
|
|
461
|
+
declare const NARROW_MAX_WIDTH_PX = 1023;
|
|
462
|
+
declare function useIsNarrow(): boolean;
|
|
463
|
+
|
|
287
464
|
interface UseWaitingNotificationsResult {
|
|
288
465
|
unreadCount: number;
|
|
289
466
|
conversations: ConversationSummary[];
|
|
@@ -435,6 +612,41 @@ interface WelcomeFarewellFormProps {
|
|
|
435
612
|
}
|
|
436
613
|
declare function WelcomeFarewellForm({ welcomeMessage, onWelcomeMessageChange, farewellMessage, onFarewellMessageChange, onSave, saving, saveSuccess, welcomePlaceholders, farewellPlaceholders, labels: labelsOverride, }: WelcomeFarewellFormProps): react.JSX.Element;
|
|
437
614
|
|
|
615
|
+
declare const TEMPLATE_SETTINGS_TAB: {
|
|
616
|
+
readonly SELECT: "select";
|
|
617
|
+
readonly CREATE: "create";
|
|
618
|
+
};
|
|
619
|
+
type TemplateSettingsTab = (typeof TEMPLATE_SETTINGS_TAB)[keyof typeof TEMPLATE_SETTINGS_TAB];
|
|
620
|
+
interface WhatsAppTemplatesSettingsLabels {
|
|
621
|
+
selectTab: string;
|
|
622
|
+
createTab: string;
|
|
623
|
+
}
|
|
624
|
+
declare const DEFAULT_TEMPLATES_SETTINGS_LABELS: WhatsAppTemplatesSettingsLabels;
|
|
625
|
+
interface WhatsAppTemplatesSettingsProps {
|
|
626
|
+
templates: WhatsAppTemplateSummary[];
|
|
627
|
+
loadingTemplates?: boolean;
|
|
628
|
+
templatesError?: boolean;
|
|
629
|
+
onRefreshTemplates?: () => void;
|
|
630
|
+
selectedTemplateName: string;
|
|
631
|
+
onSelectTemplate: (name: string, template: WhatsAppTemplateSummary | undefined) => void;
|
|
632
|
+
variables: string[];
|
|
633
|
+
onVariablesChange: (variables: string[]) => void;
|
|
634
|
+
availableVariables?: WhatsAppTemplateVariableSuggestion[];
|
|
635
|
+
saving?: boolean;
|
|
636
|
+
saveSuccess?: boolean;
|
|
637
|
+
onSave: (event: FormEvent) => void;
|
|
638
|
+
/** Ausente = host não sabe criar template; a aba de criação nem aparece. */
|
|
639
|
+
create?: {
|
|
640
|
+
value: WhatsAppCreateTemplateState;
|
|
641
|
+
onChange: (value: WhatsAppCreateTemplateState) => void;
|
|
642
|
+
onSubmit: (event: FormEvent) => void;
|
|
643
|
+
submitting?: boolean;
|
|
644
|
+
result?: WhatsAppCreateTemplateResult | null;
|
|
645
|
+
};
|
|
646
|
+
labels?: Partial<WhatsAppTemplatesSettingsLabels>;
|
|
647
|
+
}
|
|
648
|
+
declare function WhatsAppTemplatesSettings({ labels: labelsOverride, create, ...settingsProps }: WhatsAppTemplatesSettingsProps): react.JSX.Element;
|
|
649
|
+
|
|
438
650
|
interface TopicItem {
|
|
439
651
|
key: string;
|
|
440
652
|
label: string;
|
|
@@ -515,7 +727,7 @@ interface UseConversationDocumentsResult {
|
|
|
515
727
|
error: Error | undefined;
|
|
516
728
|
refetch: () => Promise<void>;
|
|
517
729
|
}
|
|
518
|
-
declare function useConversationDocuments(conversationId: string, params?: UseConversationDocumentsParams): UseConversationDocumentsResult;
|
|
730
|
+
declare function useConversationDocuments(conversationId: string | undefined, params?: UseConversationDocumentsParams): UseConversationDocumentsResult;
|
|
519
731
|
|
|
520
732
|
type ConversationRealtimeHandler = (event: MessageEvent) => void;
|
|
521
733
|
declare function useConversationRealtime(conversationId: string | undefined, onEvent: ConversationRealtimeHandler): void;
|
|
@@ -539,4 +751,4 @@ interface AsyncResourceState<T> {
|
|
|
539
751
|
refetch: () => Promise<void>;
|
|
540
752
|
}
|
|
541
753
|
|
|
542
|
-
export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, Avatar, type AvatarProps, type ConversationDocument, ConversationListItem, type ConversationListItemProps, type ConversationLocales, ConversationLocalesProvider, type ConversationLocalesProviderProps, type ConversationRealtimeHandler, type ConversationSummary, ConversationWallpaper, type ConversationWallpaperProps, type ConversationsApi,
|
|
754
|
+
export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, Avatar, type AvatarProps, type BuildTranscriptTextParams, CHANNEL_BRAND_COLOR, CONVERSATION_WINDOW, ChannelIcon, type ChannelIconProps, ConversationChannel, type ConversationContextEntry, ConversationContextPanel, type ConversationContextPanelClassNames, type ConversationContextPanelLabels, type ConversationContextPanelProps, ConversationDocument, ConversationDocumentsPanel, type ConversationDocumentsPanelClassNames, type ConversationDocumentsPanelLabels, type ConversationDocumentsPanelProps, ConversationHeader, type ConversationHeaderClassNames, type ConversationHeaderLabels, type ConversationHeaderProps, ConversationListItem, type ConversationListItemProps, type ConversationLocales, ConversationLocalesProvider, type ConversationLocalesProviderProps, type ConversationRealtimeHandler, ConversationRow, type ConversationRowClassNames, type ConversationRowProps, ConversationSummary, ConversationWallpaper, type ConversationWallpaperProps, type ConversationWindow, ConversationsApi, ConversationsFeatures, ConversationsProvider, DEFAULT_CONVERSATION_CONTEXT_LABELS, DEFAULT_CONVERSATION_DOCUMENTS_LABELS, DEFAULT_CONVERSATION_HEADER_LABELS, DEFAULT_TEMPLATES_SETTINGS_LABELS, DEFAULT_WINDOW_EXPIRED_LABELS, DateDivider, type DateDividerClassNames, type DateDividerProps, EmojiPicker, type EmojiPickerProps, FileIcon, type FileIconProps, Lightbox, type LightboxProps, MediaRenderer, type MediaRendererProps, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerClassNames, type MessageComposerProps, MessagePayload, MessageTail, MessageText, type MessageTextProps, MessageTimestamp, NARROW_MAX_WIDTH_PX, type ResolveMediaUrl, SSEProvider, SimpleEmojiPicker, type SimpleEmojiPickerProps, StatusTicks, type StatusTicksProps, TEMPLATE_SETTINGS_TAB, type TemplateSettingsTab, ToastProvider, type TopicItem, TopicsForm, type TopicsFormLabels, type TopicsFormProps, type UseConversationContextResult, type UseConversationDocumentsParams, type UseConversationDocumentsResult, type UseConversationListParams, type UseConversationListResult, type UseConversationMessagesResult, WINDOW_FILTERS, WelcomeFarewellForm, type WelcomeFarewellFormLabels, type WelcomeFarewellFormProps, WhatsAppCreateTemplateForm, type WhatsAppCreateTemplateFormLabels, type WhatsAppCreateTemplateFormProps, type WhatsAppCreateTemplateResult, type WhatsAppCreateTemplateState, WhatsAppMessageEditor, type WhatsAppMessageEditorProps, type WhatsAppTemplateHeaderType, WhatsAppTemplateSettingsForm, type WhatsAppTemplateSettingsFormLabels, type WhatsAppTemplateSettingsFormProps, type WhatsAppTemplateSummary, type WhatsAppTemplateVariableSuggestion, WhatsAppTemplatesSettings, type WhatsAppTemplatesSettingsLabels, type WhatsAppTemplatesSettingsProps, WindowExpiredNotice, type WindowExpiredNoticeLabels, type WindowExpiredNoticeProps, type WindowOfParams, buildTranscriptFilename, buildTranscriptText, downloadTextFile, formatFileSize, formatPhone, formatStalledFor, formatTimestamp, htmlToWA, isWindowBlocking, parseWhatsAppFormatting, phoneInitials, toast, useConversationContext, useConversationDocuments, useConversationList, useConversationLocales, useConversationMessages, useConversationRealtime, useConversations, useDarkMode, useGlobalRealtime, useIsDarkTheme, useIsNarrow, useToast, useWaitingNotifications, waToHTML, waToHTMLInline, windowOf };
|