@adatechnology/conversations-ui 0.1.0-rc.34 → 0.1.0-rc.36
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/{types-De5aN-E_.d.ts → ConversationSimulatorPanel--5fIzXWY.d.ts} +303 -1
- package/dist/{chunk-CUYYYZWD.js → chunk-BJNRLLDO.js} +393 -2
- package/dist/index.d.ts +69 -8
- package/dist/index.js +123 -92
- package/dist/preview/index.d.ts +5 -210
- package/dist/preview/index.js +36 -242
- package/package.json +1 -1
- package/src/index.ts +12 -0
- package/src/preview/ConversationPreview.tsx +56 -34
- package/src/preview/ConversationSimulatorClient.ts +143 -0
- package/src/preview/ConversationSimulatorPanel.tsx +46 -4
- package/src/preview/index.ts +19 -1
- package/src/settings/MessagesWorkspace.tsx +113 -10
- package/src/settings/WhatsAppTemplatesSettings.test.tsx +61 -0
- package/src/settings/WhatsAppTemplatesSettings.tsx +14 -2
- package/src/workspace/ConversationsWorkspace.tsx +82 -5
- package/src/workspace/index.ts +6 -1
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* Customização: `labels` (texto e idioma), `renderFilters` / `renderBulkActions` /
|
|
10
10
|
* `renderAboveTranscript` / `renderRow` (peças do produto), `contextEntriesOf` (vocabulário do
|
|
11
11
|
* contexto), `extraUtilities` (ações no cabeçalho da conversa) e `simulator` (painel de preview).
|
|
12
|
+
*
|
|
13
|
+
* O simulador é montado aqui, e não pelo host: com `simulator.transports` o produto entrega só o
|
|
14
|
+
* transporte de cada canal e a moldura vem do pacote. Isso traz o `preview/` para o bundle de quem
|
|
15
|
+
* usa o workspace — o preço de a tela composta ser o padrão de consumo, que é o que impede a inbox
|
|
16
|
+
* de divergir entre produtos.
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
19
|
import { useEffect, useMemo, useState, type ReactNode } from 'react'
|
|
@@ -20,6 +25,14 @@ import type { ConversationHeaderUtility } from '../ConversationHeader'
|
|
|
20
25
|
import type { QuickReply } from '../MessageComposer'
|
|
21
26
|
import type { RichComposerVariable } from '../RichMessageComposer'
|
|
22
27
|
import type { ConversationSummary } from '../providers/types'
|
|
28
|
+
import { useConversations } from '../providers/ConversationsProvider'
|
|
29
|
+
import { DEFAULT_CONVERSATION_CHANNEL, formatContactHandle, type ConversationChannel } from '../conversationChannel'
|
|
30
|
+
import type { ConversationSimulatorClient } from '../preview/ConversationSimulatorClient'
|
|
31
|
+
import type { PreviewUploadedMedia } from '../preview/createPreviewMediaUploader'
|
|
32
|
+
import {
|
|
33
|
+
ConversationSimulatorPanel,
|
|
34
|
+
type ConversationSimulatorPanelLabels,
|
|
35
|
+
} from '../preview/ConversationSimulatorPanel'
|
|
23
36
|
import { BulkTemplateModal } from './BulkTemplateModal'
|
|
24
37
|
import { ConversationPane } from './ConversationPane'
|
|
25
38
|
import { ConversationsInboxList } from './ConversationsInboxList'
|
|
@@ -27,17 +40,49 @@ import { DEFAULT_CONVERSATIONS_WORKSPACE_LABELS, type ConversationsWorkspaceLabe
|
|
|
27
40
|
import { useConversationsInbox, type UseConversationsInboxResult } from './useConversationsInbox'
|
|
28
41
|
import { TooltipLayer } from '../Tooltip'
|
|
29
42
|
|
|
43
|
+
export type SimulatorTransportParams = {
|
|
44
|
+
readonly conversationId: string
|
|
45
|
+
readonly channel: ConversationChannel
|
|
46
|
+
/** Identificador do contato no canal: telefone no WhatsApp, id de sessão no chat do site. */
|
|
47
|
+
readonly handle: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Fábrica do transporte daquele canal.
|
|
52
|
+
*
|
|
53
|
+
* É o único ponto onde o host precisa saber de canal: o painel, a moldura e o comportamento são os
|
|
54
|
+
* mesmos em todos. No WhatsApp devolve o cliente-ponte (assinatura no servidor do host); no chat do
|
|
55
|
+
* site, o cliente das rotas do widget.
|
|
56
|
+
*/
|
|
57
|
+
export type SimulatorTransportFactory = (params: SimulatorTransportParams) => ConversationSimulatorClient
|
|
58
|
+
|
|
30
59
|
export interface ConversationsWorkspaceSimulator {
|
|
31
60
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
61
|
+
* Um transporte por canal — o workspace monta o painel com o da conversa selecionada.
|
|
62
|
+
*
|
|
63
|
+
* Canal sem transporte não desenha o botão: capacidade é opcional por ausência, e oferecer
|
|
64
|
+
* "simular" numa conversa que não tem como receber a mensagem é um botão que falha ao ser tocado.
|
|
65
|
+
*/
|
|
66
|
+
readonly transports?: Partial<Record<ConversationChannel, SimulatorTransportFactory>>
|
|
67
|
+
/**
|
|
68
|
+
* Válvula de escape: o host desenha o painel inteiro. Tem precedência sobre `transports`.
|
|
69
|
+
*
|
|
70
|
+
* Era a única porta antes de `transports`, quando o simulador só falava WhatsApp e cada produto
|
|
71
|
+
* remontava a moldura — o que fez duas telas da mesma casa divergirem. Continua aceito para não
|
|
72
|
+
* quebrar quem já a usa.
|
|
34
73
|
*/
|
|
35
|
-
render(params: { conversationId: string; close: () => void }): ReactNode
|
|
74
|
+
render?(params: { conversationId: string; channel: ConversationChannel; close: () => void }): ReactNode
|
|
36
75
|
/** Ausente = ligado. Serve para esconder fora de desenvolvimento sem condicionar o JSX. */
|
|
37
76
|
readonly enabled?: boolean
|
|
38
77
|
/** Ícone da biblioteca (lucide) no utilitário do cabeçalho. Ausente, entra o frasco de teste. */
|
|
39
78
|
readonly icon?: ReactNode
|
|
40
79
|
readonly label?: string
|
|
80
|
+
/** Vocabulário do painel. O que muda de canal (destino, placeholder) já vem resolvido. */
|
|
81
|
+
readonly labels?: Partial<ConversationSimulatorPanelLabels>
|
|
82
|
+
/** Destino do upload no canal que entrega mídia por referência (o caminho da Meta). */
|
|
83
|
+
readonly uploadMedia?: (file: File) => Promise<PreviewUploadedMedia>
|
|
84
|
+
/** Recarrega o transcript a cada N ms. Serve a host sem stream. */
|
|
85
|
+
readonly pollIntervalMs?: number
|
|
41
86
|
}
|
|
42
87
|
|
|
43
88
|
export interface ConversationsWorkspaceProps {
|
|
@@ -157,9 +202,23 @@ export function ConversationsWorkspace({
|
|
|
157
202
|
}, [initialConversationId, initialWhatsappNumber, openedFromLink, inbox])
|
|
158
203
|
|
|
159
204
|
const selected = inbox.selectedConversation
|
|
160
|
-
const
|
|
205
|
+
const conversations = useConversations()
|
|
206
|
+
const selectedId = selected?.id
|
|
207
|
+
const channel = selected?.channel ?? DEFAULT_CONVERSATION_CHANNEL
|
|
208
|
+
const handle = selected ? (selected.contactId ?? selected.whatsappNumber) : ''
|
|
209
|
+
const transport = simulator?.transports?.[channel]
|
|
210
|
+
const simulatorEnabled = Boolean(
|
|
211
|
+
simulator && (simulator.enabled ?? true) && (simulator.render ?? transport),
|
|
212
|
+
)
|
|
161
213
|
const showSimulator = simulatorEnabled && simulatorOpen && Boolean(selected)
|
|
162
214
|
|
|
215
|
+
// O transporte é recriado só quando a conversa (ou o canal dela) muda: identidade nova a cada
|
|
216
|
+
// render reiniciaria a leitura do transcript dentro do painel a cada digitação.
|
|
217
|
+
const simulatorClient = useMemo(
|
|
218
|
+
() => (transport && selectedId ? transport({ conversationId: selectedId, channel, handle }) : undefined),
|
|
219
|
+
[transport, selectedId, channel, handle],
|
|
220
|
+
)
|
|
221
|
+
|
|
163
222
|
const paneUtilities = useMemo(() => {
|
|
164
223
|
if (!selected) return undefined
|
|
165
224
|
const fromProduct = extraUtilitiesFor?.(selected) ?? []
|
|
@@ -325,7 +384,25 @@ export function ConversationsWorkspace({
|
|
|
325
384
|
// `min-height:0` junto do `min-width:0`: sem isso a linha do grid cresce com o conteúdo do
|
|
326
385
|
// painel, o scroll interno nunca ativa e quem rola passa a ser a página inteira.
|
|
327
386
|
<div className="cv-workspace-simulator">
|
|
328
|
-
{simulator?.render
|
|
387
|
+
{simulator?.render ? (
|
|
388
|
+
simulator.render({ conversationId: selected.id, channel, close: () => setSimulatorOpen(false) })
|
|
389
|
+
) : simulatorClient && conversations ? (
|
|
390
|
+
// `key` pela conversa: trocar de contato sem remontar deixaria o transcript e o campo
|
|
391
|
+
// de texto do contato anterior na tela.
|
|
392
|
+
<ConversationSimulatorPanel
|
|
393
|
+
key={selected.id}
|
|
394
|
+
client={simulatorClient}
|
|
395
|
+
sse={conversations.sse}
|
|
396
|
+
conversationId={selected.id}
|
|
397
|
+
channel={channel}
|
|
398
|
+
displayHandle={formatContactHandle({ handle, channel })}
|
|
399
|
+
loadMessages={(conversationId) => conversations.api.fetchMessages(conversationId)}
|
|
400
|
+
onClose={() => setSimulatorOpen(false)}
|
|
401
|
+
{...(simulator?.labels ? { labels: simulator.labels } : {})}
|
|
402
|
+
{...(simulator?.uploadMedia ? { uploadMedia: simulator.uploadMedia } : {})}
|
|
403
|
+
{...(simulator?.pollIntervalMs ? { pollIntervalMs: simulator.pollIntervalMs } : {})}
|
|
404
|
+
/>
|
|
405
|
+
) : null}
|
|
329
406
|
</div>
|
|
330
407
|
) : null}
|
|
331
408
|
</div>
|
package/src/workspace/index.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export { ConversationsWorkspace } from './ConversationsWorkspace'
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
ConversationsWorkspaceProps,
|
|
4
|
+
ConversationsWorkspaceSimulator,
|
|
5
|
+
SimulatorTransportFactory,
|
|
6
|
+
SimulatorTransportParams,
|
|
7
|
+
} from './ConversationsWorkspace'
|
|
3
8
|
export { ConversationPane } from './ConversationPane'
|
|
4
9
|
export type { ConversationPaneProps } from './ConversationPane'
|
|
5
10
|
export { BulkTemplateModal } from './BulkTemplateModal'
|