@adatechnology/conversations-ui 0.1.0 → 0.2.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.
Files changed (49) hide show
  1. package/dist/{ConversationSimulatorPanel--5fIzXWY.d.ts → ConversationSimulatorPanel-ikgmlciE.d.ts} +103 -1
  2. package/dist/{chunk-BJNRLLDO.js → chunk-NHQDQJL2.js} +769 -244
  3. package/dist/index.d.ts +304 -9
  4. package/dist/index.js +2166 -509
  5. package/dist/preview/index.d.ts +2 -2
  6. package/dist/preview/index.js +147 -1
  7. package/dist/styles.css +135 -0
  8. package/package.json +2 -2
  9. package/src/MessageComposer.test.tsx +14 -0
  10. package/src/MessageComposer.tsx +327 -73
  11. package/src/RichMessageComposer.test.tsx +22 -4
  12. package/src/RichMessageComposer.tsx +674 -370
  13. package/src/index.ts +28 -1
  14. package/src/preview/createMockConversationsApi.ts +184 -0
  15. package/src/providers/types.ts +34 -0
  16. package/src/quickReplies/AttachmentsFormSection.tsx +160 -0
  17. package/src/quickReplies/QuickRepliesPicker.test.tsx +114 -0
  18. package/src/quickReplies/QuickRepliesPicker.tsx +188 -0
  19. package/src/quickReplies/QuickRepliesWorkspace.test.tsx +32 -0
  20. package/src/quickReplies/QuickRepliesWorkspace.tsx +395 -0
  21. package/src/quickReplies/createUploadQueue.test.ts +106 -0
  22. package/src/quickReplies/createUploadQueue.ts +69 -0
  23. package/src/quickReplies/index.ts +5 -0
  24. package/src/quickReplies/labels.ts +124 -0
  25. package/src/quickReplies/quickReply.types.ts +74 -0
  26. package/src/quickReplies/quickReplyAttachmentUpload.test.ts +76 -0
  27. package/src/quickReplies/quickReplyAttachmentUpload.ts +81 -0
  28. package/src/quickReplies/quickReplyAttachments.test.ts +396 -0
  29. package/src/quickReplies/quickReplyAttachments.ts +289 -0
  30. package/src/quickReplies/quickReplySearch.test.ts +88 -0
  31. package/src/quickReplies/quickReplySearch.ts +104 -0
  32. package/src/quickReplies/quickReplyShortcut.test.ts +38 -0
  33. package/src/quickReplies/quickReplyShortcut.ts +46 -0
  34. package/src/quickReplies/resolveConversationVariables.test.ts +63 -0
  35. package/src/quickReplies/resolveConversationVariables.ts +41 -0
  36. package/src/quickReplies/useQuickRepliesPicker.test.ts +20 -0
  37. package/src/quickReplies/useQuickRepliesPicker.ts +121 -0
  38. package/src/quickReplies/useQuickRepliesWorkspace.test.ts +222 -0
  39. package/src/quickReplies/useQuickRepliesWorkspace.ts +426 -0
  40. package/src/quickReplies/useQuickReplyAttachmentUploads.ts +159 -0
  41. package/src/styles.css +87 -0
  42. package/src/workspace/ConversationPane.tsx +137 -73
  43. package/src/workspace/ConversationsWorkspace.tsx +16 -1
  44. package/src/workspace/QueuedAttachmentsList.test.ts +27 -0
  45. package/src/workspace/QueuedAttachmentsList.tsx +198 -0
  46. package/src/workspace/index.ts +1 -0
  47. package/src/workspace/labels.ts +14 -0
  48. package/src/workspace/useComposerAttachmentRetry.ts +155 -0
  49. package/src/workspace/useComposerQueue.ts +220 -0
@@ -1,5 +1,5 @@
1
- import { U as MessagePayload, u as ConversationSummary, m as ConversationEventSource, w as ConversationsApi, Q as ListConversationsParams, n as ConversationPage, a5 as SSEProvider, k as ConversationDocument, a6 as SendPreviewMediaParams, Z as PreviewUploadedMedia, $ as PreviewWebhookClient, a3 as ResolveMediaUrl } from '../ConversationSimulatorPanel--5fIzXWY.js';
2
- export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, o as ConversationPreview, p as ConversationPreviewProps, q as ConversationSimulatorClient, r as ConversationSimulatorPanel, s as ConversationSimulatorPanelLabels, t as ConversationSimulatorPanelProps, B as CreatePreviewMediaUploaderParams, D as CreatePreviewWebhookClientParams, E as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, G as DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS, I as DEFAULT_MEDIA_UPLOAD_PATH, W as PreviewInProductionError, X as PreviewMediaUploadRejectedError, Y as PreviewMediaUploadRequest, _ as PreviewUploadedMedia, a0 as PreviewWebhookRejectedError, a4 as SIMULATOR_FILE_MEDIA_KINDS, a7 as SendSimulatorMediaParams, a8 as SimulatorMediaKind, a9 as ToSimulatorClientParams, ac as acceptsMediaKind, ad as assertPreviewEnvironment, ah as createPreviewMediaPoster, ai as createPreviewMediaUploader, aj as createPreviewWebhookClient, al as isConversationSimulatorClient, am as mediaKindOf, an as mediaTypeOf, ao as signPreviewPayload, ap as simulatorPanelLabelsOf, aq as toConversationSimulatorClient } from '../ConversationSimulatorPanel--5fIzXWY.js';
1
+ import { V as MessagePayload, u as ConversationSummary, m as ConversationEventSource, x as ConversationsApi, R as ListConversationsParams, n as ConversationPage, ab as SSEProvider, k as ConversationDocument, ac as SendPreviewMediaParams, _ as PreviewUploadedMedia, a0 as PreviewWebhookClient, a9 as ResolveMediaUrl } from '../ConversationSimulatorPanel-ikgmlciE.js';
2
+ export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, o as ConversationPreview, p as ConversationPreviewProps, q as ConversationSimulatorClient, r as ConversationSimulatorPanel, s as ConversationSimulatorPanelLabels, t as ConversationSimulatorPanelProps, D as CreatePreviewMediaUploaderParams, E as CreatePreviewWebhookClientParams, F as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, H as DEFAULT_CONVERSATION_SIMULATOR_PANEL_LABELS, J as DEFAULT_MEDIA_UPLOAD_PATH, X as PreviewInProductionError, Y as PreviewMediaUploadRejectedError, Z as PreviewMediaUploadRequest, $ as PreviewUploadedMedia, a1 as PreviewWebhookRejectedError, aa as SIMULATOR_FILE_MEDIA_KINDS, ad as SendSimulatorMediaParams, ae as SimulatorMediaKind, ag as ToSimulatorClientParams, aj as acceptsMediaKind, ak as assertPreviewEnvironment, ao as createPreviewMediaPoster, ap as createPreviewMediaUploader, aq as createPreviewWebhookClient, as as isConversationSimulatorClient, at as mediaKindOf, au as mediaTypeOf, av as signPreviewPayload, aw as simulatorPanelLabelsOf, ax as toConversationSimulatorClient } from '../ConversationSimulatorPanel-ikgmlciE.js';
3
3
  import { InteractiveReplyOption } from '@adatechnology/meta-whatsapp-contracts/testing';
4
4
  import * as react from 'react';
5
5
  export { PREVIEW_MEDIA_ID_PREFIX } from '@adatechnology/meta-whatsapp-contracts';
@@ -16,7 +16,7 @@ import {
16
16
  mediaTypeOf,
17
17
  simulatorPanelLabelsOf,
18
18
  toConversationSimulatorClient
19
- } from "../chunk-BJNRLLDO.js";
19
+ } from "../chunk-NHQDQJL2.js";
20
20
  import "../chunk-WCBDXZ3X.js";
21
21
 
22
22
  // src/preview/previewStore.ts
@@ -648,6 +648,37 @@ var PREVIEW_TEMPLATES = [
648
648
  { name: "lembrete_documentos", language: "pt_BR", status: "APPROVED", category: "UTILITY" },
649
649
  { name: "promocao_taxa", language: "pt_BR", status: "PENDING", category: "MARKETING" }
650
650
  ];
651
+ var PREVIEW_QUICK_REPLIES = [
652
+ { id: "1", title: "Boas-vindas", shortcut: "ola", body: "Ol\xE1 {{nome}}, bem-vindo!" },
653
+ {
654
+ id: "2",
655
+ title: "Lista de documentos",
656
+ shortcut: "documentos",
657
+ body: "Segue a lista de documentos necess\xE1rios para prosseguir com sua solicita\xE7\xE3o.",
658
+ attachments: [
659
+ { uploadId: "upload-1", filename: "Checklist de documentos.pdf", mimeType: "application/pdf", sizeBytes: 245e3 },
660
+ { uploadId: "upload-2", filename: "Tabela de taxas.png", mimeType: "image/png", sizeBytes: 125e3 }
661
+ ]
662
+ },
663
+ {
664
+ id: "3",
665
+ title: "Prazo de an\xE1lise",
666
+ shortcut: "prazo",
667
+ body: "Sua solicita\xE7\xE3o est\xE1 em an\xE1lise e o resultado sair\xE1 em at\xE9 48 horas."
668
+ },
669
+ {
670
+ id: "4",
671
+ title: "Agendar liga\xE7\xE3o",
672
+ shortcut: "ligacao",
673
+ body: "Ol\xE1 {{nome}}, voc\xEA gostaria de agendar uma liga\xE7\xE3o comigo? Que horas funcionam melhor para voc\xEA?"
674
+ },
675
+ {
676
+ id: "5",
677
+ title: "Encerramento",
678
+ shortcut: "tchau",
679
+ body: "Obrigado {{nome_completo}}, foi um prazer atender voc\xEA. Qualquer d\xFAvida, \xE9 s\xF3 chamar!"
680
+ }
681
+ ];
651
682
  function createMockConversationsApi(params) {
652
683
  const latencyMs = params.latencyMs ?? DEFAULT_LATENCY_MS;
653
684
  async function withLatency(produce) {
@@ -826,6 +857,121 @@ ${names.join("\n")}`], { type: "application/zip" });
826
857
  },
827
858
  listTemplates() {
828
859
  return withLatency(() => [...PREVIEW_TEMPLATES]);
860
+ },
861
+ listQuickReplies(searchParams) {
862
+ return withLatency(() => {
863
+ let results = [...PREVIEW_QUICK_REPLIES];
864
+ if (searchParams?.search) {
865
+ const term = searchParams.search.toLowerCase();
866
+ results = results.filter(
867
+ (qr) => qr.title.toLowerCase().includes(term) || qr.shortcut.toLowerCase().includes(term) || qr.body.toLowerCase().includes(term)
868
+ );
869
+ }
870
+ return results;
871
+ });
872
+ },
873
+ createQuickReply(input) {
874
+ return withLatency(() => {
875
+ if (PREVIEW_QUICK_REPLIES.some((qr) => qr.shortcut === input.shortcut)) {
876
+ const error = new Error("Atalho j\xE1 existe");
877
+ error.code = "QUICK_REPLY_SHORTCUT_TAKEN";
878
+ throw error;
879
+ }
880
+ const newQuickReply = {
881
+ id: String(Date.now()),
882
+ title: input.title,
883
+ shortcut: input.shortcut,
884
+ body: input.body
885
+ };
886
+ PREVIEW_QUICK_REPLIES.push(newQuickReply);
887
+ return newQuickReply;
888
+ });
889
+ },
890
+ updateQuickReply(id, input) {
891
+ return withLatency(() => {
892
+ const index = PREVIEW_QUICK_REPLIES.findIndex((qr) => qr.id === id);
893
+ if (index === -1) {
894
+ throw new Error("Mensagem pronta n\xE3o encontrada");
895
+ }
896
+ if (PREVIEW_QUICK_REPLIES.some((qr) => qr.id !== id && qr.shortcut === input.shortcut)) {
897
+ const error = new Error("Atalho j\xE1 existe");
898
+ error.code = "QUICK_REPLY_SHORTCUT_TAKEN";
899
+ throw error;
900
+ }
901
+ const updated = {
902
+ id,
903
+ title: input.title,
904
+ shortcut: input.shortcut,
905
+ body: input.body
906
+ };
907
+ PREVIEW_QUICK_REPLIES[index] = updated;
908
+ return updated;
909
+ });
910
+ },
911
+ deleteQuickReply(id) {
912
+ return withLatency(() => {
913
+ const index = PREVIEW_QUICK_REPLIES.findIndex((qr) => qr.id === id);
914
+ if (index !== -1) {
915
+ PREVIEW_QUICK_REPLIES.splice(index, 1);
916
+ }
917
+ });
918
+ },
919
+ uploadQuickReplyAttachment(file, options) {
920
+ return new Promise((resolve, reject) => {
921
+ if (options.signal.aborted) {
922
+ reject(new DOMException("Aborted", "AbortError"));
923
+ return;
924
+ }
925
+ const abortHandler = () => {
926
+ reject(new DOMException("Aborted", "AbortError"));
927
+ };
928
+ options.signal.addEventListener("abort", abortHandler);
929
+ const steps = 6;
930
+ const stepDuration = 1500 / steps;
931
+ let step = 0;
932
+ const progressInterval = setInterval(() => {
933
+ if (options.signal.aborted) {
934
+ clearInterval(progressInterval);
935
+ options.signal.removeEventListener("abort", abortHandler);
936
+ reject(new DOMException("Aborted", "AbortError"));
937
+ return;
938
+ }
939
+ step += 1;
940
+ const progress = step / steps;
941
+ if (progress <= 1) {
942
+ options.onProgress(Math.round(progress * 100));
943
+ }
944
+ if (step >= steps) {
945
+ clearInterval(progressInterval);
946
+ options.signal.removeEventListener("abort", abortHandler);
947
+ resolve({
948
+ uploadId: `upload-${Date.now()}-${Math.random().toString(36).substring(7)}`,
949
+ filename: file.name,
950
+ mimeType: file.type,
951
+ sizeBytes: file.size
952
+ });
953
+ }
954
+ }, stepDuration);
955
+ });
956
+ },
957
+ sendStoredAttachments(params2) {
958
+ return withLatency(() => {
959
+ const results = [];
960
+ for (let index = 0; index < params2.uploadIds.length; index += 1) {
961
+ const uploadId = params2.uploadIds[index];
962
+ const isFailed = uploadId.includes("falha");
963
+ if (isFailed) {
964
+ results.push({ uploadId, status: "failed" });
965
+ continue;
966
+ }
967
+ if (index > 0 && results[index - 1]?.status === "failed") {
968
+ results.push({ uploadId, status: "skipped" });
969
+ continue;
970
+ }
971
+ results.push({ uploadId, status: "sent" });
972
+ }
973
+ return { results };
974
+ });
829
975
  }
830
976
  };
831
977
  }
package/dist/styles.css CHANGED
@@ -899,6 +899,141 @@
899
899
  .dark .cv-workspace-attachments li {
900
900
  border-color: rgb(51 65 85);
901
901
  }
902
+ .cv-attachment-item {
903
+ max-width: 16rem;
904
+ }
905
+ .cv-attachment-item__thumbnail {
906
+ display: block;
907
+ flex: none;
908
+ width: 2rem;
909
+ height: 2rem;
910
+ overflow: hidden;
911
+ border-radius: 0.375rem;
912
+ background: rgb(226 232 240);
913
+ }
914
+ .dark .cv-attachment-item__thumbnail {
915
+ background: rgb(51 65 85);
916
+ }
917
+ .cv-attachment-item__thumbnail img {
918
+ width: 100%;
919
+ height: 100%;
920
+ object-fit: cover;
921
+ }
922
+ .cv-attachment-item__info {
923
+ display: flex;
924
+ flex-direction: column;
925
+ overflow: hidden;
926
+ min-width: 0;
927
+ }
928
+ .cv-attachment-item__name {
929
+ overflow: hidden;
930
+ text-overflow: ellipsis;
931
+ white-space: nowrap;
932
+ font-weight: 500;
933
+ }
934
+ .cv-attachment-item__meta {
935
+ font-size: 0.6875rem;
936
+ color: rgb(100 116 139);
937
+ }
938
+ .dark .cv-attachment-item__meta {
939
+ color: rgb(148 163 184);
940
+ }
941
+ .cv-attachment-item--failed .cv-attachment-item__meta {
942
+ color: rgb(220 38 38);
943
+ }
944
+ .dark .cv-attachment-item--failed .cv-attachment-item__meta {
945
+ color: rgb(248 113 113);
946
+ }
947
+ .cv-attachment-item--sent .cv-attachment-item__meta {
948
+ color: rgb(22 163 74);
949
+ }
950
+ .dark .cv-attachment-item--sent .cv-attachment-item__meta {
951
+ color: rgb(74 222 128);
952
+ }
953
+ .cv-attachment-item--skipped .cv-attachment-item__meta {
954
+ color: rgb(180 83 9);
955
+ }
956
+ .dark .cv-attachment-item--skipped .cv-attachment-item__meta {
957
+ color: rgb(251 191 36);
958
+ }
959
+ .cv-attachment-item__retry {
960
+ flex: none;
961
+ border: 0;
962
+ background: none;
963
+ padding: 0;
964
+ font-size: 0.6875rem;
965
+ text-decoration: underline;
966
+ color: rgb(37 99 235);
967
+ cursor: pointer;
968
+ }
969
+ .cv-attachment-item {
970
+ transition: opacity 200ms ease, transform 200ms ease;
971
+ opacity: 1;
972
+ transform: scale(1);
973
+ }
974
+ .cv-attachment-item--departing {
975
+ opacity: 0;
976
+ transform: scale(0.92);
977
+ }
978
+ @media (prefers-reduced-motion: reduce) {
979
+ .cv-attachment-item {
980
+ transition: none;
981
+ }
982
+ }
983
+ .cv-skeleton-line {
984
+ border-radius: 0.25rem;
985
+ background:
986
+ linear-gradient(
987
+ 90deg,
988
+ rgb(226 232 240) 25%,
989
+ rgb(241 245 249) 50%,
990
+ rgb(226 232 240) 75%);
991
+ background-size: 200% 100%;
992
+ animation: cv-skeleton-shimmer 1.4s ease-in-out infinite;
993
+ }
994
+ .dark .cv-skeleton-line {
995
+ background:
996
+ linear-gradient(
997
+ 90deg,
998
+ rgb(51 65 85) 25%,
999
+ rgb(71 85 105) 50%,
1000
+ rgb(51 65 85) 75%);
1001
+ background-size: 200% 100%;
1002
+ }
1003
+ @keyframes cv-skeleton-shimmer {
1004
+ 0% {
1005
+ background-position: 200% 0;
1006
+ }
1007
+ 100% {
1008
+ background-position: -200% 0;
1009
+ }
1010
+ }
1011
+ @media (prefers-reduced-motion: reduce) {
1012
+ .cv-skeleton-line {
1013
+ animation: none;
1014
+ }
1015
+ }
1016
+ .cv-skeleton-line--title {
1017
+ width: 70%;
1018
+ height: 0.75rem;
1019
+ }
1020
+ .cv-skeleton-line--shortcut {
1021
+ width: 50%;
1022
+ height: 0.75rem;
1023
+ }
1024
+ .cv-skeleton-line--body {
1025
+ width: 90%;
1026
+ height: 0.75rem;
1027
+ }
1028
+ .cv-row-departing {
1029
+ transition: opacity 200ms ease;
1030
+ opacity: 0;
1031
+ }
1032
+ @media (prefers-reduced-motion: reduce) {
1033
+ .cv-row-departing {
1034
+ transition: none;
1035
+ }
1036
+ }
902
1037
  .cv-filter-count {
903
1038
  display: inline-flex;
904
1039
  align-items: center;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adatechnology/conversations-ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "WhatsApp conversation UI components — parametrizável por endpoint, tema e feature flags",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -31,7 +31,7 @@
31
31
  "clsx": "^2.1.1",
32
32
  "lucide-react": "^1.21.0",
33
33
  "tailwind-merge": "^3.6.0",
34
- "@adatechnology/meta-whatsapp-contracts": "0.2.0"
34
+ "@adatechnology/meta-whatsapp-contracts": "0.3.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@xyflow/react": "^12",
@@ -32,4 +32,18 @@ describe('MessageComposer', () => {
32
32
  expect(markup).toContain('aria-label="Enviar"')
33
33
  expect(markup).not.toContain('aria-label="Gravar áudio"')
34
34
  })
35
+
36
+ it('sem savedQuickReplies, não desenha o botão de raio nem o combobox', () => {
37
+ const withPort = renderToStaticMarkup(
38
+ <MessageComposer
39
+ onSend={() => {}}
40
+ savedQuickReplies={{ listQuickReplies: async () => [], conversationId: 'c1' }}
41
+ />,
42
+ )
43
+ const withoutPort = renderToStaticMarkup(<MessageComposer onSend={() => {}} />)
44
+
45
+ expect(withPort).toContain('aria-label="Mensagens prontas"')
46
+ expect(withoutPort).not.toContain('aria-label="Mensagens prontas"')
47
+ expect(withoutPort).not.toContain('role="combobox"')
48
+ })
35
49
  })