@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.
@@ -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
- * Desenha o painel do cliente. Fica com o host porque o simulador precisa da rota assinada no
33
- * servidor DELE — e é o que mantém o `preview/` fora do bundle de quem não usa.
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 simulatorEnabled = Boolean(simulator && (simulator.enabled ?? true))
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({ conversationId: selected.id, close: () => setSimulatorOpen(false) })}
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>
@@ -1,5 +1,10 @@
1
1
  export { ConversationsWorkspace } from './ConversationsWorkspace'
2
- export type { ConversationsWorkspaceProps, ConversationsWorkspaceSimulator } from './ConversationsWorkspace'
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'