analytica-frontend-lib 1.7.21 → 1.7.22

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 (119) hide show
  1. package/dist/ActivitiesHistory/index.js +7 -7
  2. package/dist/ActivitiesHistory/index.mjs +7 -7
  3. package/dist/ActivityCardQuestionBanks/index.js +7 -7
  4. package/dist/ActivityCardQuestionBanks/index.mjs +7 -7
  5. package/dist/ActivityCardQuestionPreview/index.js +7 -7
  6. package/dist/ActivityCardQuestionPreview/index.mjs +7 -7
  7. package/dist/ActivityCreate/ActivityCreateContent/index.js +7 -7
  8. package/dist/ActivityCreate/ActivityCreateContent/index.mjs +7 -7
  9. package/dist/ActivityCreate/index.js +7 -7
  10. package/dist/ActivityCreate/index.mjs +7 -7
  11. package/dist/ActivityDetails/index.js +7 -7
  12. package/dist/ActivityDetails/index.mjs +7 -7
  13. package/dist/ActivityFilters/index.js +7 -7
  14. package/dist/ActivityFilters/index.mjs +7 -7
  15. package/dist/ActivityPageLayout/index.js +7 -7
  16. package/dist/ActivityPageLayout/index.mjs +7 -7
  17. package/dist/ActivityPreview/index.js +7 -7
  18. package/dist/ActivityPreview/index.mjs +7 -7
  19. package/dist/AlertManager/index.js +7 -7
  20. package/dist/AlertManager/index.mjs +7 -7
  21. package/dist/CheckBoxGroup/index.js +7 -7
  22. package/dist/CheckBoxGroup/index.mjs +7 -7
  23. package/dist/EnemMoment/index.js +7 -7
  24. package/dist/EnemMoment/index.mjs +7 -7
  25. package/dist/EnemMomentStudentModal/index.js +7 -7
  26. package/dist/EnemMomentStudentModal/index.mjs +7 -7
  27. package/dist/ExamDetailsLayout/ExamDetailsPage/index.js +7 -7
  28. package/dist/ExamDetailsLayout/ExamDetailsPage/index.mjs +7 -7
  29. package/dist/ExamPageLayout/index.js +7 -7
  30. package/dist/ExamPageLayout/index.mjs +7 -7
  31. package/dist/KnowledgePerformanceCard/index.js +7 -7
  32. package/dist/KnowledgePerformanceCard/index.mjs +7 -7
  33. package/dist/LessonCardPreview/index.js +7 -7
  34. package/dist/LessonCardPreview/index.mjs +7 -7
  35. package/dist/LessonMediaSections/index.js +7 -7
  36. package/dist/LessonMediaSections/index.mjs +7 -7
  37. package/dist/LessonsCatalog/index.js +7 -7
  38. package/dist/LessonsCatalog/index.mjs +7 -7
  39. package/dist/PrintableUsersTable/index.js +7 -7
  40. package/dist/PrintableUsersTable/index.mjs +7 -7
  41. package/dist/QuestionsPerformanceCard/index.js +7 -7
  42. package/dist/QuestionsPerformanceCard/index.mjs +7 -7
  43. package/dist/RecommendedLessonCreate/index.js +7 -7
  44. package/dist/RecommendedLessonCreate/index.mjs +7 -7
  45. package/dist/RecommendedLessonsHistory/index.js +7 -7
  46. package/dist/RecommendedLessonsHistory/index.mjs +7 -7
  47. package/dist/SendActivityModal/SendActivityModal.js +7 -7
  48. package/dist/SendActivityModal/SendActivityModal.mjs +7 -7
  49. package/dist/SendActivityModal/index.js +7 -7
  50. package/dist/SendActivityModal/index.mjs +7 -7
  51. package/dist/SendLessonModal/index.js +7 -7
  52. package/dist/SendLessonModal/index.mjs +7 -7
  53. package/dist/SimulatedPerformance/SimulatedPerformanceView/index.js +7 -7
  54. package/dist/SimulatedPerformance/SimulatedPerformanceView/index.mjs +7 -7
  55. package/dist/SimulationsPage/index.js +7 -7
  56. package/dist/SimulationsPage/index.mjs +7 -7
  57. package/dist/SupportChatComposer/index.js +3 -3
  58. package/dist/SupportChatComposer/index.mjs +2 -2
  59. package/dist/SupportChatEvent/index.js +3 -3
  60. package/dist/SupportChatEvent/index.mjs +2 -2
  61. package/dist/SupportChatMessage/index.js +3 -3
  62. package/dist/SupportChatMessage/index.mjs +2 -2
  63. package/dist/SupportChatWidget/index.js +3 -3
  64. package/dist/SupportChatWidget/index.mjs +2 -2
  65. package/dist/SupportConversationMessages/index.js +5 -5
  66. package/dist/SupportConversationMessages/index.mjs +4 -4
  67. package/dist/SupportTicketConversation/index.js +7 -7
  68. package/dist/SupportTicketConversation/index.mjs +6 -6
  69. package/dist/TableProvider/index.js +7 -7
  70. package/dist/TableProvider/index.mjs +7 -7
  71. package/dist/UnifiedDraftModelPage/index.js +7 -7
  72. package/dist/UnifiedDraftModelPage/index.mjs +7 -7
  73. package/dist/UnifiedHistoryPage/index.js +7 -7
  74. package/dist/UnifiedHistoryPage/index.mjs +7 -7
  75. package/dist/{chunk-I6EAPPBL.mjs → chunk-25SRDJZ6.mjs} +3 -3
  76. package/dist/{chunk-CYSODZGX.js → chunk-27Y6V3GM.js} +169 -66
  77. package/dist/chunk-27Y6V3GM.js.map +1 -0
  78. package/dist/{chunk-KSUPPAKH.js → chunk-3IXK6AR7.js} +5 -5
  79. package/dist/{chunk-KSUPPAKH.js.map → chunk-3IXK6AR7.js.map} +1 -1
  80. package/dist/{chunk-POT3ALY3.js → chunk-5DINXU6W.js} +1 -1
  81. package/dist/{chunk-POT3ALY3.js.map → chunk-5DINXU6W.js.map} +1 -1
  82. package/dist/{chunk-EMWX6IKP.js → chunk-7VJP5D26.js} +13 -13
  83. package/dist/{chunk-EMWX6IKP.js.map → chunk-7VJP5D26.js.map} +1 -1
  84. package/dist/{chunk-7ZB3YHF2.mjs → chunk-C6LC5LD5.mjs} +1 -1
  85. package/dist/{chunk-7ZB3YHF2.mjs.map → chunk-C6LC5LD5.mjs.map} +1 -1
  86. package/dist/{chunk-OES54VU6.mjs → chunk-CFV2OZ3P.mjs} +116 -13
  87. package/dist/chunk-CFV2OZ3P.mjs.map +1 -0
  88. package/dist/{chunk-I4QGQMXI.mjs → chunk-EXYXZFNK.mjs} +2 -2
  89. package/dist/{chunk-FU23WOTY.js → chunk-HSV5VZ4Z.js} +6 -6
  90. package/dist/{chunk-FU23WOTY.js.map → chunk-HSV5VZ4Z.js.map} +1 -1
  91. package/dist/{chunk-KWXOYHYK.mjs → chunk-P7MORXSE.mjs} +2 -2
  92. package/dist/{chunk-YG4IZ5Q3.mjs → chunk-Q2OSEROT.mjs} +2 -2
  93. package/dist/{chunk-K45PTE7F.mjs → chunk-RBKQXT73.mjs} +4 -4
  94. package/dist/{chunk-VSVLIRO2.js → chunk-RXEU77VN.js} +15 -15
  95. package/dist/{chunk-VSVLIRO2.js.map → chunk-RXEU77VN.js.map} +1 -1
  96. package/dist/{chunk-ZB6HDDE4.js → chunk-TQO37PVW.js} +14 -14
  97. package/dist/{chunk-ZB6HDDE4.js.map → chunk-TQO37PVW.js.map} +1 -1
  98. package/dist/components/SupportChatWidget/guestSupportChatAdapter.d.ts +6 -1
  99. package/dist/components/SupportChatWidget/guestSupportChatAdapter.d.ts.map +1 -1
  100. package/dist/hooks/useAppContent/index.js +7 -7
  101. package/dist/hooks/useAppContent/index.mjs +7 -7
  102. package/dist/hooks/useSupportChat.d.ts +2 -0
  103. package/dist/hooks/useSupportChat.d.ts.map +1 -1
  104. package/dist/index.js +8 -8
  105. package/dist/index.mjs +7 -7
  106. package/dist/types/supportChat/index.d.ts +14 -0
  107. package/dist/types/supportChat/index.d.ts.map +1 -1
  108. package/dist/types/supportChat/index.js +2 -2
  109. package/dist/types/supportChat/index.mjs +1 -1
  110. package/dist/types/supportChat.d.ts +14 -0
  111. package/dist/types/supportChat.d.ts.map +1 -1
  112. package/package.json +1 -1
  113. package/dist/chunk-CYSODZGX.js.map +0 -1
  114. package/dist/chunk-OES54VU6.mjs.map +0 -1
  115. /package/dist/{chunk-I6EAPPBL.mjs.map → chunk-25SRDJZ6.mjs.map} +0 -0
  116. /package/dist/{chunk-I4QGQMXI.mjs.map → chunk-EXYXZFNK.mjs.map} +0 -0
  117. /package/dist/{chunk-KWXOYHYK.mjs.map → chunk-P7MORXSE.mjs.map} +0 -0
  118. /package/dist/{chunk-YG4IZ5Q3.mjs.map → chunk-Q2OSEROT.mjs.map} +0 -0
  119. /package/dist/{chunk-K45PTE7F.mjs.map → chunk-RBKQXT73.mjs.map} +0 -0
@@ -20,7 +20,7 @@ import {
20
20
  SUPPORT_CHAT_HANDLER,
21
21
  SUPPORT_CHAT_MAX_ATTACHMENTS,
22
22
  SUPPORT_CHAT_STATUS
23
- } from "./chunk-7ZB3YHF2.mjs";
23
+ } from "./chunk-C6LC5LD5.mjs";
24
24
  import {
25
25
  isSupportTicketOrigin
26
26
  } from "./chunk-MVT7IVMY.mjs";
@@ -89,8 +89,31 @@ var useSupportChat = ({
89
89
  const [messages, setMessages] = useState([]);
90
90
  const [sending, setSending] = useState(false);
91
91
  const [error, setError] = useState(null);
92
+ const [restored, setRestored] = useState(false);
92
93
  const sendingRef = useRef(false);
93
94
  const ticketId = ticket?.id;
95
+ const userEmail = user?.email;
96
+ const ticketRef = useRef(ticket);
97
+ ticketRef.current = ticket;
98
+ useEffect(() => {
99
+ if (!adapter.restoreConversation) return;
100
+ let active = true;
101
+ adapter.restoreConversation({ email: userEmail }).then((resumed) => {
102
+ if (!active || !resumed) return;
103
+ if (ticketRef.current || sendingRef.current) return;
104
+ setTicket(resumed.ticket);
105
+ setMessages(resumed.messages);
106
+ setRestored(true);
107
+ }).catch(() => {
108
+ });
109
+ return () => {
110
+ active = false;
111
+ };
112
+ }, [adapter, userEmail]);
113
+ const closed = ticket?.status === SUPPORT_CHAT_STATUS.FECHADO;
114
+ useEffect(() => {
115
+ if (closed) adapter.forgetConversation?.();
116
+ }, [adapter, closed]);
94
117
  useEffect(() => {
95
118
  if (!ticketId) return;
96
119
  const handleEvent = (event) => {
@@ -180,16 +203,19 @@ var useSupportChat = ({
180
203
  [adapter, ticketId]
181
204
  );
182
205
  const reset = useCallback(() => {
206
+ adapter.forgetConversation?.();
183
207
  setTicket(null);
184
208
  setMessages([]);
185
209
  setError(null);
186
- }, []);
210
+ setRestored(false);
211
+ }, [adapter]);
187
212
  return {
188
213
  ticket,
189
214
  messages,
190
215
  sending,
191
216
  error,
192
- closed: ticket?.status === SUPPORT_CHAT_STATUS.FECHADO,
217
+ closed,
218
+ restored,
193
219
  send,
194
220
  updateContact: adapter.updateContact ? updateContact : void 0,
195
221
  reset
@@ -740,7 +766,16 @@ var SupportChatWidgetContent = ({
740
766
  const listRef = useRef3(null);
741
767
  const fileInputRef = useRef3(null);
742
768
  const seenCount = useRef3(0);
743
- const { messages, sending, error, closed, send, updateContact, reset } = useSupportChat({
769
+ const {
770
+ messages,
771
+ sending,
772
+ error,
773
+ closed,
774
+ restored,
775
+ send,
776
+ updateContact,
777
+ reset
778
+ } = useSupportChat({
744
779
  adapter,
745
780
  channel,
746
781
  institutionId,
@@ -776,10 +811,10 @@ var SupportChatWidgetContent = ({
776
811
  useEffect4(() => {
777
812
  const fresh = messages.slice(seenCount.current);
778
813
  seenCount.current = messages.length;
779
- if (isOpen) return;
814
+ if (isOpen || restored && fresh.length === messages.length) return;
780
815
  const replies = fresh.filter((message) => !isOwnMessage(message)).length;
781
816
  if (replies) setUnread((count) => count + replies);
782
- }, [messages, isOpen]);
817
+ }, [messages, isOpen, restored]);
783
818
  useEffect4(() => {
784
819
  if (!isOpen || article) return;
785
820
  setUnread(0);
@@ -1041,6 +1076,30 @@ var SupportChatWidgetContent = ({
1041
1076
  import { useMemo as useMemo2 } from "react";
1042
1077
 
1043
1078
  // src/components/SupportChatWidget/guestSupportChatAdapter.ts
1079
+ var DEFAULT_STORAGE_KEY = "analytica:support-chat";
1080
+ var readStoredSession = (key) => {
1081
+ try {
1082
+ const raw = globalThis.localStorage?.getItem(key);
1083
+ if (!raw) return null;
1084
+ const session = JSON.parse(raw);
1085
+ return session.ticketId && session.token ? session : null;
1086
+ } catch {
1087
+ return null;
1088
+ }
1089
+ };
1090
+ var writeStoredSession = (key, session) => {
1091
+ try {
1092
+ globalThis.localStorage?.setItem(key, JSON.stringify(session));
1093
+ } catch {
1094
+ }
1095
+ };
1096
+ var clearStoredSession = (key) => {
1097
+ try {
1098
+ globalThis.localStorage?.removeItem(key);
1099
+ } catch {
1100
+ }
1101
+ };
1102
+ var statusOf = (error) => error?.response?.status;
1044
1103
  var GUEST_TOKEN_HEADER = "x-support-guest-token";
1045
1104
  var senderNameFor = (role, requesterName) => {
1046
1105
  if (role === SUPPORT_CHAT_AUTHOR.AGENT) return "Suporte";
@@ -1063,7 +1122,8 @@ var fromSupportAnswer = (answer, requesterName) => ({
1063
1122
  var createGuestSupportChatAdapter = ({
1064
1123
  apiClient,
1065
1124
  wsUrl,
1066
- pollIntervalMs = 3e3
1125
+ pollIntervalMs = 3e3,
1126
+ storageKey = DEFAULT_STORAGE_KEY
1067
1127
  }) => {
1068
1128
  const sessions = /* @__PURE__ */ new Map();
1069
1129
  const hiddenIds = /* @__PURE__ */ new Set();
@@ -1131,15 +1191,22 @@ var createGuestSupportChatAdapter = ({
1131
1191
  ...isSupportTicketOrigin(channel) ? { origin: channel } : {}
1132
1192
  });
1133
1193
  const ticketId = data.data.id;
1134
- sessions.set(ticketId, {
1135
- token: data.data.guestToken,
1136
- name: requesterName || "Visitante"
1137
- });
1194
+ const name = requesterName || "Visitante";
1195
+ sessions.set(ticketId, { token: data.data.guestToken, name });
1138
1196
  let messages = await listMessages(ticketId);
1197
+ let hiddenId;
1139
1198
  if (!message && messages[0]) {
1140
- hiddenIds.add(messages[0].id);
1199
+ hiddenId = messages[0].id;
1200
+ hiddenIds.add(hiddenId);
1141
1201
  messages = messages.slice(1);
1142
1202
  }
1203
+ writeStoredSession(storageKey, {
1204
+ ticketId,
1205
+ token: data.data.guestToken,
1206
+ name,
1207
+ userEmail: email ?? null,
1208
+ hiddenId
1209
+ });
1143
1210
  if (files.length) messages.push(await sendMessage(ticketId, "", files));
1144
1211
  return {
1145
1212
  ticket: {
@@ -1167,8 +1234,44 @@ var createGuestSupportChatAdapter = ({
1167
1234
  const session = sessions.get(ticketId);
1168
1235
  if (session && contact.requesterName) {
1169
1236
  session.name = contact.requesterName;
1237
+ const stored = readStoredSession(storageKey);
1238
+ if (stored?.ticketId === ticketId) {
1239
+ writeStoredSession(storageKey, {
1240
+ ...stored,
1241
+ name: contact.requesterName
1242
+ });
1243
+ }
1170
1244
  }
1171
1245
  },
1246
+ async restoreConversation({ email }) {
1247
+ const stored = readStoredSession(storageKey);
1248
+ if (!stored) return null;
1249
+ if (stored.userEmail !== (email ?? null)) {
1250
+ clearStoredSession(storageKey);
1251
+ return null;
1252
+ }
1253
+ const { ticketId } = stored;
1254
+ sessions.set(ticketId, { token: stored.token, name: stored.name });
1255
+ if (stored.hiddenId) hiddenIds.add(stored.hiddenId);
1256
+ try {
1257
+ const { data } = await apiClient.get(
1258
+ `/support/guest/${ticketId}`,
1259
+ { headers: headersFor(ticketId) }
1260
+ );
1261
+ if (data.data.status === SUPPORT_CHAT_STATUS.FECHADO) {
1262
+ clearStoredSession(storageKey);
1263
+ return null;
1264
+ }
1265
+ return { ticket: data.data, messages: await listMessages(ticketId) };
1266
+ } catch (error) {
1267
+ if (statusOf(error) === 404) clearStoredSession(storageKey);
1268
+ sessions.delete(ticketId);
1269
+ return null;
1270
+ }
1271
+ },
1272
+ forgetConversation() {
1273
+ clearStoredSession(storageKey);
1274
+ },
1172
1275
  listMessages,
1173
1276
  subscribe(ticketId, listener) {
1174
1277
  const seen = /* @__PURE__ */ new Map();
@@ -1433,4 +1536,4 @@ export {
1433
1536
  NativeSupportChat,
1434
1537
  createMockSupportChatAdapter
1435
1538
  };
1436
- //# sourceMappingURL=chunk-OES54VU6.mjs.map
1539
+ //# sourceMappingURL=chunk-CFV2OZ3P.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/SupportChatWidget/SupportChatWidget.tsx","../src/hooks/useSupportChat.ts","../src/components/SupportChatWidget/SupportChatContactForm.tsx","../src/components/SupportChatWidget/SupportChatArticleView.tsx","../src/components/SupportChatWidget/supportChatContent.tsx","../src/components/SupportChatWidget/NativeSupportChat.tsx","../src/components/SupportChatWidget/guestSupportChatAdapter.ts","../src/components/SupportChatWidget/mockSupportChatAdapter.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useRef,\n useState,\n type ChangeEvent,\n type KeyboardEvent,\n} from 'react';\nimport {\n ChatCircleDotsIcon,\n MinusIcon,\n PaperclipIcon,\n PaperPlaneTiltIcon,\n XIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport { useSupportChat } from '../../hooks/useSupportChat';\nimport { SupportChatContactForm } from './SupportChatContactForm';\nimport { SupportChatArticleView } from './SupportChatArticleView';\nimport {\n AttachmentList,\n MessageText,\n PendingAttachment,\n SUPPORT_CHAT_ACCEPT,\n pickAttachments,\n} from './supportChatContent';\nimport { SupportType } from '../../types/support';\nimport {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_MAX_ATTACHMENTS,\n type SupportChatAdapter,\n type SupportChatFlags,\n type SupportChatMessage,\n type SupportChatTheme,\n type SupportChatUser,\n} from '../../types/supportChat';\nimport { cn } from '../../utils/utils';\n\n/** Visibility of the chat window */\nexport type SupportChatWindowState = 'closed' | 'open' | 'minimized';\n\nexport interface SupportChatWidgetProps {\n /** Data layer (createMockSupportChatAdapter for now) */\n adapter: SupportChatAdapter;\n /** App the widget runs in, sent with the ticket (e.g. \"login\", \"backoffice\") */\n channel: string;\n institutionId?: string;\n /** Logged user; omit for visitors */\n user?: SupportChatUser;\n flags?: SupportChatFlags;\n theme?: SupportChatTheme;\n onOpen?: () => void;\n onSend?: (message: SupportChatMessage) => void;\n /**\n * Called when the window is closed with X. Closing only hides it: the\n * conversation stays connected and new replies count as unread.\n */\n onClose?: () => void;\n /**\n * Link of a FAQ article, e.g. `(id) => '/artigo/' + id`. With it, the\n * \"Baseado na FAQ\" line under an AI answer opens the article; without it\n * the line only names the article.\n */\n faqArticleUrl?: (articleId: string) => string;\n}\n\n/** Label read by screen readers on the unread badge of the launcher */\nconst formatNewMessagesLabel = (count: number): string =>\n count === 1 ? '1 nova mensagem' : `${count} novas mensagens`;\n\n/** The requester side of the conversation (user or visitor) */\nconst isOwnMessage = (message: SupportChatMessage) =>\n message.senderRole === SUPPORT_CHAT_AUTHOR.USER ||\n message.senderRole === SUPPORT_CHAT_AUTHOR.GUEST;\n\nconst formatTime = (createdAt: string) =>\n new Date(createdAt).toLocaleTimeString('pt-BR', {\n hour: '2-digit',\n minute: '2-digit',\n });\n\n/** \"Baseado na FAQ: <title>\" under an AI answer, linked when possible */\nconst FaqReference = ({\n faq,\n faqArticleUrl,\n onOpenArticle,\n}: {\n faq: { id: string; title: string };\n faqArticleUrl?: (articleId: string) => string;\n onOpenArticle?: (article: { id: string; title: string }) => void;\n}) => {\n const linked = !!onOpenArticle || !!faqArticleUrl;\n const label = (\n <Text\n as=\"span\"\n size=\"xs\"\n color=\"text-primary-700\"\n className={cn(linked && 'underline')}\n >\n Baseado na FAQ: {faq.title}\n </Text>\n );\n let content = label;\n if (onOpenArticle) {\n // Opens inside the chat, like the Zendesk messenger (no new tab)\n content = (\n <a\n href={faqArticleUrl?.(faq.id) ?? '#'}\n onClick={(event) => {\n event.preventDefault();\n onOpenArticle(faq);\n }}\n >\n {label}\n </a>\n );\n } else if (faqArticleUrl) {\n content = (\n <a href={faqArticleUrl(faq.id)} target=\"_blank\" rel=\"noreferrer\">\n {label}\n </a>\n );\n }\n return (\n <div data-testid=\"support-chat-faq\" className=\"mt-1 max-w-[80%]\">\n {content}\n </div>\n );\n};\n\nconst MessageBubble = ({\n message,\n faqArticleUrl,\n onOpenArticle,\n}: {\n message: SupportChatMessage;\n faqArticleUrl?: (articleId: string) => string;\n onOpenArticle?: (article: { id: string; title?: string }) => void;\n}) => {\n const own = isOwnMessage(message);\n return (\n <div\n className={cn(\n 'flex flex-col mb-3 transition-opacity',\n own ? 'items-end' : 'items-start',\n message.pending && 'opacity-60'\n )}\n aria-busy={message.pending || undefined}\n >\n {!own && (\n <Text size=\"xs\" className=\"text-text-500 mb-1\">\n {message.senderName}\n </Text>\n )}\n <div\n className={cn(\n 'max-w-[80%] rounded-2xl whitespace-pre-wrap break-words',\n // Image-only messages show the image without the colored bubble\n message.content\n ? cn(\n 'px-4 py-2',\n own\n ? 'bg-primary-500 text-white rounded-br-md'\n : 'bg-background-100 text-text-900 rounded-bl-md'\n )\n : 'text-text-900'\n )}\n >\n {message.content && (\n <MessageText\n text={message.content}\n onOpenArticle={\n onOpenArticle ? (id) => onOpenArticle({ id }) : undefined\n }\n />\n )}\n {message.attachments.length > 0 && (\n <AttachmentList items={message.attachments} />\n )}\n </div>\n {message.faq && (\n <FaqReference\n faq={message.faq}\n faqArticleUrl={faqArticleUrl}\n onOpenArticle={onOpenArticle}\n />\n )}\n <Text size=\"xs\" className=\"text-text-400 mt-1\">\n {message.pending ? 'Enviando…' : formatTime(message.createdAt)}\n </Text>\n </div>\n );\n};\n\n/**\n * Floating support chat (native support, the Zendesk replacement).\n * Renders nothing when disabled or when the institution uses Zendesk.\n */\nexport const SupportChatWidget = ({\n flags,\n ...props\n}: SupportChatWidgetProps) => {\n const hidden =\n flags?.enabled === false ||\n flags?.chat === false ||\n flags?.supportType === SupportType.ZENDESK;\n\n if (hidden) return null;\n return <SupportChatWidgetContent {...props} />;\n};\n\nconst SupportChatWidgetContent = ({\n adapter,\n channel,\n institutionId,\n user,\n theme,\n onOpen,\n onSend,\n onClose,\n faqArticleUrl,\n}: Omit<SupportChatWidgetProps, 'flags'>) => {\n const [windowState, setWindowState] =\n useState<SupportChatWindowState>('closed');\n const [draft, setDraft] = useState('');\n const [files, setFiles] = useState<File[]>([]);\n const [fileError, setFileError] = useState<string | null>(null);\n const [unread, setUnread] = useState(0);\n // FAQ article open inside the chat (the conversation stays as it was)\n const [article, setArticle] = useState<{ id: string; title?: string } | null>(\n null\n );\n const [expanded, setExpanded] = useState(false);\n const listRef = useRef<HTMLDivElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const seenCount = useRef(0);\n\n const {\n messages,\n sending,\n error,\n closed,\n restored,\n send,\n updateContact,\n reset,\n } = useSupportChat({\n adapter,\n channel,\n institutionId,\n user,\n });\n const [savedContactRequests, setSavedContactRequests] = useState<string[]>(\n []\n );\n\n // Latest AI request for name/e-mail: the form goes under it, and the\n // composer waits until it is sent\n const contactRequest = updateContact\n ? [...messages].reverse().find((message) => message.contactRequest?.length)\n : undefined;\n const awaitingContact =\n !!contactRequest && !savedContactRequests.includes(contactRequest.id);\n\n const isOpen = windowState === 'open';\n\n const canOpenArticle = !!adapter.getArticle && !!institutionId;\n const loadArticle = useCallback(\n (articleId: string) => {\n if (!adapter.getArticle || !institutionId) {\n return Promise.reject(new Error('Artigo indisponível'));\n }\n // Called on the adapter so a class-based adapter keeps its `this`\n return adapter.getArticle(institutionId, articleId);\n },\n [adapter, institutionId]\n );\n const openArticle = canOpenArticle\n ? (target: { id: string; title?: string }) => {\n setArticle(target);\n setExpanded(true);\n }\n : undefined;\n const closeArticle = () => {\n setArticle(null);\n setExpanded(false);\n };\n const title = theme?.title ?? 'Suporte';\n const left = theme?.position === 'bottom-left';\n const accentStyle = theme?.primaryColor\n ? { backgroundColor: theme.primaryColor }\n : undefined;\n\n // Count the support replies that arrive while the window is not visible\n useEffect(() => {\n const fresh = messages.slice(seenCount.current);\n seenCount.current = messages.length;\n // A resumed conversation was already read before the reload\n if (isOpen || (restored && fresh.length === messages.length)) return;\n const replies = fresh.filter((message) => !isOwnMessage(message)).length;\n if (replies) setUnread((count) => count + replies);\n }, [messages, isOpen, restored]);\n\n useEffect(() => {\n if (!isOpen || article) return;\n setUnread(0);\n listRef.current?.scrollTo?.({ top: listRef.current.scrollHeight });\n }, [isOpen, messages, article]);\n\n const open = () => {\n setWindowState('open');\n onOpen?.();\n };\n\n const close = () => {\n setWindowState('closed');\n onClose?.();\n };\n\n /** Drop a finished conversation so the next message opens a new ticket */\n const startOver = () => {\n setDraft('');\n setFiles([]);\n setFileError(null);\n setUnread(0);\n seenCount.current = 0;\n setSavedContactRequests([]);\n reset();\n };\n\n const submit = async () => {\n const content = draft;\n const pending = files;\n if (!content.trim() && pending.length === 0) return;\n setDraft('');\n setFiles([]);\n setFileError(null);\n const message = await send(content, pending);\n if (message) {\n onSend?.(message);\n } else {\n setDraft(content);\n setFiles(pending);\n }\n };\n\n const handleFiles = (event: ChangeEvent<HTMLInputElement>) => {\n const { accepted, problem } = pickAttachments(\n files,\n Array.from(event.target.files ?? [])\n );\n setFiles(accepted);\n setFileError(problem);\n event.target.value = '';\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n void submit();\n }\n };\n\n return (\n <div\n className={cn(\n 'fixed bottom-4 z-50 flex flex-col gap-3',\n left ? 'left-4 items-start' : 'right-4 items-end'\n )}\n >\n {isOpen && (\n <section\n role=\"dialog\"\n aria-label={title}\n className={cn(\n 'max-w-[calc(100vw-2rem)] max-h-[calc(100vh-6rem)] flex flex-col bg-background rounded-2xl shadow-lg border border-border-100 overflow-hidden transition-[width,height]',\n expanded ? 'w-[720px] h-[615px]' : 'w-[360px] h-[520px]'\n )}\n >\n {article ? (\n <SupportChatArticleView\n articleId={article.id}\n initialTitle={article.title}\n loadArticle={loadArticle}\n expanded={expanded}\n onToggleExpanded={() => setExpanded((value) => !value)}\n onBack={closeArticle}\n externalUrl={faqArticleUrl?.(article.id)}\n accentStyle={accentStyle}\n />\n ) : (\n <>\n <header\n className=\"flex items-center justify-between px-4 py-3 bg-primary-500 text-white\"\n style={accentStyle}\n >\n <Text size=\"md\" weight=\"semibold\" color=\"\">\n {title}\n </Text>\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"raw\"\n aria-label=\"Minimizar\"\n onClick={() => setWindowState('minimized')}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <MinusIcon size={18} />\n </Button>\n <Button\n variant=\"raw\"\n aria-label=\"Fechar\"\n onClick={close}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <XIcon size={18} />\n </Button>\n </div>\n </header>\n\n <div ref={listRef} className=\"flex-1 overflow-y-auto px-4 py-3\">\n {messages.length === 0 ? (\n <Text size=\"sm\" className=\"text-text-500 text-center mt-8\">\n Olá! Como podemos te ajudar?\n </Text>\n ) : (\n messages.map((message) => (\n <div key={message.id}>\n <MessageBubble\n message={message}\n faqArticleUrl={faqArticleUrl}\n onOpenArticle={openArticle}\n />\n {updateContact &&\n message.id === contactRequest?.id &&\n message.contactRequest && (\n <SupportChatContactForm\n fields={message.contactRequest}\n accentColor={theme?.primaryColor}\n onSubmit={async (contact) => {\n const ok = await updateContact(contact);\n if (ok) {\n setSavedContactRequests((ids) => [\n ...ids,\n message.id,\n ]);\n }\n return ok;\n }}\n />\n )}\n </div>\n ))\n )}\n </div>\n\n {(fileError || error) && (\n <Text\n size=\"xs\"\n role=\"alert\"\n className=\"px-4 py-2 text-error-700 bg-error-50\"\n >\n {fileError ?? error}\n </Text>\n )}\n\n {closed ? (\n <div className=\"flex flex-col items-center gap-2 px-4 py-3 border-t border-border-100\">\n <Text size=\"sm\" color=\"text-text-500\" className=\"text-center\">\n Este atendimento foi encerrado.\n </Text>\n <Button variant=\"link\" size=\"small\" onClick={startOver}>\n Iniciar nova conversa\n </Button>\n </div>\n ) : (\n <form\n className=\"flex flex-col gap-2 px-3 py-3 border-t border-border-100\"\n onSubmit={(event) => {\n event.preventDefault();\n void submit();\n }}\n >\n {files.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {files.map((file, index) => (\n <PendingAttachment\n key={`${file.name}-${index}`}\n file={file}\n onRemove={() =>\n setFiles((list) =>\n list.filter((_, i) => i !== index)\n )\n }\n />\n ))}\n </div>\n )}\n <div className=\"flex items-end gap-2\">\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={SUPPORT_CHAT_ACCEPT}\n multiple\n hidden\n data-testid=\"support-chat-file-input\"\n onChange={handleFiles}\n />\n <Button\n variant=\"raw\"\n aria-label=\"Anexar arquivo\"\n disabled={\n sending ||\n awaitingContact ||\n files.length >= SUPPORT_CHAT_MAX_ATTACHMENTS\n }\n onClick={() => fileInputRef.current?.click()}\n className=\"p-2 rounded-full text-text-600 hover:bg-background-100 disabled:opacity-50\"\n >\n <PaperclipIcon size={20} />\n </Button>\n <textarea\n aria-label=\"Mensagem\"\n placeholder={\n awaitingContact\n ? 'Preencha seus dados acima'\n : 'Digite sua mensagem'\n }\n disabled={awaitingContact}\n rows={1}\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={handleKeyDown}\n className=\"flex-1 resize-none rounded-xl border border-border-300 px-3 py-2 text-sm text-text-900 focus:outline-none focus:border-primary-500 max-h-28\"\n />\n <Button\n variant=\"raw\"\n type=\"submit\"\n aria-label=\"Enviar\"\n disabled={\n sending ||\n awaitingContact ||\n (!draft.trim() && files.length === 0)\n }\n className=\"p-2 rounded-full bg-primary-500 text-white disabled:opacity-50\"\n style={accentStyle}\n >\n <PaperPlaneTiltIcon size={18} />\n </Button>\n </div>\n </form>\n )}\n </>\n )}\n </section>\n )}\n\n {!isOpen && (\n <Button\n variant=\"raw\"\n aria-label={`Abrir ${title.toLowerCase()}`}\n onClick={open}\n className=\"relative w-14 h-14 rounded-full bg-primary-500 text-white shadow-lg flex items-center justify-center hover:brightness-110\"\n style={accentStyle}\n >\n <ChatCircleDotsIcon size={28} />\n {unread > 0 && (\n <Text\n as=\"span\"\n size=\"xs\"\n color=\"text-white\"\n aria-label={formatNewMessagesLabel(unread)}\n className=\"absolute -top-1 -right-1 min-w-5 h-5 px-1 rounded-full bg-error-500 flex items-center justify-center\"\n >\n {unread}\n </Text>\n )}\n </Button>\n )}\n </div>\n );\n};\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_GUEST_SENDER_ID,\n SUPPORT_CHAT_STATUS,\n type SupportChatAdapter,\n type SupportChatContact,\n type SupportChatEvent,\n type SupportChatMessage,\n type SupportChatTicket,\n type SupportChatUser,\n} from '../types/supportChat';\n\nexport interface UseSupportChatConfig {\n adapter: SupportChatAdapter;\n channel: string;\n institutionId?: string;\n user?: SupportChatUser;\n}\n\nexport interface UseSupportChatReturn {\n ticket: SupportChatTicket | null;\n messages: SupportChatMessage[];\n sending: boolean;\n error: string | null;\n /** Ticket resolved: no more messages can be sent */\n closed: boolean;\n /** The conversation was resumed from an earlier page load */\n restored: boolean;\n /** Send a message (text and/or files); the first one opens the ticket */\n send: (content: string, files?: File[]) => Promise<SupportChatMessage | null>;\n /**\n * Save the contact typed in the form shown under the AI request. Resolves\n * false (and sets `error`) when it fails. Undefined when the adapter cannot\n * save it.\n */\n updateContact?: (contact: SupportChatContact) => Promise<boolean>;\n /** Forget the current conversation */\n reset: () => void;\n}\n\nlet pendingSequence = 0;\n\n/**\n * Message shown right away while it is being sent (optimistic rendering).\n * Images use local object URLs, released when the real message arrives.\n */\nconst buildPendingMessage = (\n content: string,\n files: File[],\n user?: SupportChatUser\n): SupportChatMessage => {\n pendingSequence += 1;\n return {\n id: `pending-${pendingSequence}`,\n senderId: SUPPORT_CHAT_GUEST_SENDER_ID,\n senderName: user?.name || 'Você',\n senderPhoto: null,\n senderRole: user?.email\n ? SUPPORT_CHAT_AUTHOR.USER\n : SUPPORT_CHAT_AUTHOR.GUEST,\n content,\n messageType: 'text',\n createdAt: new Date().toISOString(),\n attachments: files.map((file, index) => ({\n id: `pending-${pendingSequence}-${index}`,\n url: URL.createObjectURL(file),\n fileName: file.name,\n mimeType: file.type,\n size: file.size,\n })),\n pending: true,\n };\n};\n\nconst releasePending = (message: SupportChatMessage) =>\n message.attachments.forEach((item) => URL.revokeObjectURL(item.url));\n\nconst getErrorMessage = (error: unknown) =>\n error instanceof Error ? error.message : 'Não foi possível enviar a mensagem';\n\n/** Add or replace a message, keeping the list ordered by arrival */\nconst upsertMessage = (\n list: SupportChatMessage[],\n message: SupportChatMessage\n): SupportChatMessage[] =>\n list.some((item) => item.id === message.id)\n ? list.map((item) => (item.id === message.id ? message : item))\n : [...list, message];\n\n/**\n * State of a support conversation on top of a SupportChatAdapter.\n * The REST-like calls write; the subscription only applies the events,\n * so a message received both ways is stored once.\n */\nexport const useSupportChat = ({\n adapter,\n channel,\n institutionId,\n user,\n}: UseSupportChatConfig): UseSupportChatReturn => {\n const [ticket, setTicket] = useState<SupportChatTicket | null>(null);\n const [messages, setMessages] = useState<SupportChatMessage[]>([]);\n const [sending, setSending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [restored, setRestored] = useState(false);\n const sendingRef = useRef(false);\n\n const ticketId = ticket?.id;\n const userEmail = user?.email;\n // Read by the restore, which must not replace a conversation started meanwhile\n const ticketRef = useRef(ticket);\n ticketRef.current = ticket;\n\n // Resume the unresolved conversation of an earlier page load, unless the\n // person already started a new one meanwhile\n useEffect(() => {\n if (!adapter.restoreConversation) return;\n let active = true;\n adapter\n .restoreConversation({ email: userEmail })\n .then((resumed) => {\n if (!active || !resumed) return;\n if (ticketRef.current || sendingRef.current) return;\n setTicket(resumed.ticket);\n setMessages(resumed.messages);\n setRestored(true);\n })\n .catch(() => {\n // Not resumed: the next message opens a new ticket\n });\n return () => {\n active = false;\n };\n }, [adapter, userEmail]);\n\n const closed = ticket?.status === SUPPORT_CHAT_STATUS.FECHADO;\n\n // A resolved conversation is not resumed on the next load\n useEffect(() => {\n if (closed) adapter.forgetConversation?.();\n }, [adapter, closed]);\n\n useEffect(() => {\n if (!ticketId) return;\n\n const handleEvent = (event: SupportChatEvent) => {\n switch (event.type) {\n case 'new_message':\n setMessages((list) => upsertMessage(list, event.payload.message));\n break;\n case 'message_updated':\n setMessages((list) =>\n list.map((item) =>\n item.id === event.payload.messageId\n ? { ...item, content: event.payload.content }\n : item\n )\n );\n break;\n case 'ticket_updated': {\n const { ticketId: id, ...rest } = event.payload;\n setTicket((current) =>\n current?.id === id ? { ...current, ...rest } : current\n );\n break;\n }\n case 'error':\n setError(event.payload.message);\n break;\n }\n };\n\n return adapter.subscribe(ticketId, handleEvent);\n }, [adapter, ticketId]);\n\n const send = useCallback(\n async (content: string, files: File[] = []) => {\n const text = content.trim();\n if ((!text && files.length === 0) || sendingRef.current) return null;\n\n sendingRef.current = true;\n setSending(true);\n setError(null);\n\n const pending = buildPendingMessage(text, files, user);\n setMessages((list) => [...list, pending]);\n // Swap the optimistic message for the confirmed ones\n const confirm = (confirmed: SupportChatMessage[]) =>\n setMessages((list) =>\n confirmed.reduce(\n upsertMessage,\n list.filter((item) => item.id !== pending.id)\n )\n );\n\n try {\n if (ticketId) {\n const message = await adapter.sendMessage(ticketId, text, files);\n confirm([message]);\n return message;\n }\n\n const started = await adapter.startConversation({\n message: text,\n channel,\n institutionId,\n requesterName: user?.name,\n email: user?.email,\n files,\n });\n setTicket(started.ticket);\n confirm(started.messages);\n return started.messages[started.messages.length - 1] ?? null;\n } catch (sendError) {\n setMessages((list) => list.filter((item) => item.id !== pending.id));\n setError(getErrorMessage(sendError));\n return null;\n } finally {\n releasePending(pending);\n sendingRef.current = false;\n setSending(false);\n }\n },\n [adapter, channel, institutionId, ticketId, user]\n );\n\n const updateContact = useCallback(\n async (contact: SupportChatContact) => {\n if (!ticketId || !adapter.updateContact) return false;\n setError(null);\n try {\n await adapter.updateContact(ticketId, contact);\n return true;\n } catch (contactError) {\n setError(\n (contactError as { response?: { status?: number } } | null)?.response\n ?.status === 409\n ? 'O e-mail deste atendimento já foi informado'\n : 'Não foi possível salvar seus dados. Tente de novo.'\n );\n return false;\n }\n },\n [adapter, ticketId]\n );\n\n const reset = useCallback(() => {\n adapter.forgetConversation?.();\n setTicket(null);\n setMessages([]);\n setError(null);\n setRestored(false);\n }, [adapter]);\n\n return {\n ticket,\n messages,\n sending,\n error,\n closed,\n restored,\n send,\n updateContact: adapter.updateContact ? updateContact : undefined,\n reset,\n };\n};\n","import { useState, type FormEvent } from 'react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport Input from '../Input/Input';\nimport type {\n SupportChatContact,\n SupportChatContactField,\n} from '../../types/supportChat';\n\nexport interface SupportChatContactFormProps {\n /** Fields the AI asked for, in order (one step each) */\n fields: SupportChatContactField[];\n /** Saves the contact; resolves true when it worked */\n onSubmit: (contact: SupportChatContact) => Promise<boolean>;\n /** Accent color of the theme, for the button */\n accentColor?: string;\n}\n\nconst STEPS: Record<\n SupportChatContactField,\n { label: string; type: string; autoComplete: string; placeholder: string }\n> = {\n name: {\n label: 'Seu nome',\n type: 'text',\n autoComplete: 'name',\n placeholder: 'Como podemos te chamar?',\n },\n email: {\n label: 'Seu e-mail',\n type: 'email',\n autoComplete: 'email',\n placeholder: 'nome@exemplo.com',\n },\n};\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Problem with a typed value, or null when it can be sent */\nconst validate = (\n field: SupportChatContactField,\n value: string\n): string | null => {\n if (field === 'name') {\n return value.length >= 2 ? null : 'Digite seu nome';\n }\n return EMAIL_PATTERN.test(value) ? null : 'Digite um e-mail válido';\n};\n\n/**\n * Step-by-step form for the contact the AI asks on the first contact (name,\n * then e-mail), shown under the AI message like the Zendesk pre-chat form.\n * The values go to the backend as fields, not as a chat message. Once sent,\n * it shows what was saved.\n */\nexport const SupportChatContactForm = ({\n fields,\n onSubmit,\n accentColor,\n}: SupportChatContactFormProps) => {\n const [step, setStep] = useState(0);\n const [values, setValues] = useState<Record<string, string>>({});\n const [problem, setProblem] = useState<string | null>(null);\n const [saving, setSaving] = useState(false);\n const [saved, setSaved] = useState(false);\n\n const field = fields[step];\n const config = STEPS[field];\n const value = values[field] ?? '';\n const isLast = step === fields.length - 1;\n\n if (saved) {\n return (\n <div\n data-testid=\"support-chat-contact-saved\"\n className=\"mb-3 ml-1 flex max-w-[85%] flex-col gap-1 rounded-2xl border border-border-100 px-4 py-3\"\n >\n {fields.map((item) => (\n <Text key={item} size=\"xs\" color=\"text-text-600\">\n {STEPS[item].label}:{' '}\n <Text as=\"span\" size=\"xs\" weight=\"semibold\" color=\"text-text-900\">\n {values[item]}\n </Text>\n </Text>\n ))}\n </div>\n );\n }\n\n const advance = async (event: FormEvent) => {\n event.preventDefault();\n const typed = field === 'email' ? value.trim().toLowerCase() : value.trim();\n const error = validate(field, typed);\n setProblem(error);\n if (error) return;\n\n const next = { ...values, [field]: typed };\n setValues(next);\n if (!isLast) {\n setStep(step + 1);\n return;\n }\n\n setSaving(true);\n const ok = await onSubmit({\n ...(next.name ? { requesterName: next.name } : {}),\n ...(next.email ? { email: next.email } : {}),\n });\n setSaving(false);\n if (ok) setSaved(true);\n };\n\n const inputId = `support-chat-contact-${field}`;\n\n return (\n <form\n aria-label=\"Seus dados\"\n noValidate\n onSubmit={(event) => void advance(event)}\n className=\"mb-3 ml-1 flex max-w-[85%] flex-col gap-3 rounded-2xl border border-border-100 px-4 py-3\"\n >\n <Input\n id={inputId}\n key={field}\n label={config.label}\n variant=\"rounded\"\n size=\"small\"\n type={config.type}\n autoComplete={config.autoComplete}\n placeholder={config.placeholder}\n value={value}\n autoFocus\n disabled={saving}\n errorMessage={problem ?? undefined}\n onChange={(event) =>\n setValues((current) => ({ ...current, [field]: event.target.value }))\n }\n />\n <div className=\"flex items-center justify-between\">\n <Text size=\"xs\" color=\"text-text-500\">\n {fields.length > 1 ? `${step + 1} de ${fields.length}` : ''}\n </Text>\n <Button\n type=\"submit\"\n size=\"small\"\n disabled={saving}\n style={accentColor ? { backgroundColor: accentColor } : undefined}\n >\n {isLast ? 'Enviar' : 'Avançar'}\n </Button>\n </div>\n </form>\n );\n};\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport {\n ArrowLeftIcon,\n ArrowSquareOutIcon,\n ArrowsInSimpleIcon,\n ArrowsOutSimpleIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport { ArticleContent } from '../ArticleContent/ArticleContent';\nimport { embedVideoLinks } from '../../utils/videoEmbed';\nimport type { SupportChatArticle } from '../../types/supportChat';\nimport { cn } from '../../utils/utils';\n\nexport interface SupportChatArticleViewProps {\n /** Article to show */\n articleId: string;\n /** Title known from the chat, shown while the article loads */\n initialTitle?: string;\n /** Loads the article (adapter.getArticle bound to the institution) */\n loadArticle: (articleId: string) => Promise<SupportChatArticle>;\n /** Whether the chat window is in the wide mode */\n expanded: boolean;\n onToggleExpanded: () => void;\n /** Back to the conversation (it is kept as it was) */\n onBack: () => void;\n /** Page of the article in the help center, for \"Abrir em nova aba\" */\n externalUrl?: string;\n /** Header background (theme) */\n accentStyle?: { backgroundColor: string };\n}\n\nconst formatUpdatedAt = (iso: string) =>\n new Date(iso).toLocaleDateString('pt-BR', {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric',\n });\n\n/**\n * FAQ article read inside the support chat, like the Zendesk messenger: the\n * conversation is replaced by the article (no new tab), with back,\n * expand/reduce and open-in-new-tab controls. The big title moves to the\n * header once it scrolls out of view. YouTube/Vimeo links of the article\n * become players.\n */\nexport const SupportChatArticleView = ({\n articleId,\n initialTitle,\n loadArticle,\n expanded,\n onToggleExpanded,\n onBack,\n externalUrl,\n accentStyle,\n}: SupportChatArticleViewProps) => {\n const [article, setArticle] = useState<SupportChatArticle | null>(null);\n const [failed, setFailed] = useState(false);\n const [titleHidden, setTitleHidden] = useState(false);\n const titleRef = useRef<HTMLHeadingElement>(null);\n\n useEffect(() => {\n let active = true;\n setArticle(null);\n setFailed(false);\n loadArticle(articleId)\n .then((loaded) => {\n if (active) setArticle(loaded);\n })\n .catch(() => {\n if (active) setFailed(true);\n });\n return () => {\n active = false;\n };\n }, [articleId, loadArticle]);\n\n // The big title leaves the screen → show it in the header\n useEffect(() => {\n const title = titleRef.current;\n if (!title || typeof IntersectionObserver === 'undefined') {\n return undefined;\n }\n const observer = new IntersectionObserver(([entry]) =>\n setTitleHidden(!entry.isIntersecting)\n );\n observer.observe(title);\n return () => observer.disconnect();\n }, [article]);\n\n const html = useMemo(\n () => (article ? embedVideoLinks(article.body) : ''),\n [article]\n );\n const title = article?.title ?? initialTitle ?? 'Artigo';\n\n return (\n <div\n data-testid=\"support-chat-article\"\n className=\"flex min-h-0 flex-1 flex-col\"\n >\n <header\n className=\"flex items-center gap-1 px-2 py-2 bg-primary-500 text-white\"\n style={accentStyle}\n >\n <Button\n variant=\"raw\"\n aria-label=\"Voltar para a janela de mensagens\"\n onClick={onBack}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <ArrowLeftIcon size={18} />\n </Button>\n <Text\n as=\"span\"\n size=\"sm\"\n weight=\"semibold\"\n color=\"\"\n aria-hidden={!titleHidden || undefined}\n className={cn(\n 'min-w-0 flex-1 truncate transition-opacity',\n titleHidden ? 'opacity-100' : 'opacity-0'\n )}\n >\n {title}\n </Text>\n <Button\n variant=\"raw\"\n aria-label={expanded ? 'Reduzir artigo' : 'Expandir artigo'}\n onClick={onToggleExpanded}\n className=\"p-1 rounded hover:bg-white/20\"\n >\n {expanded ? (\n <ArrowsInSimpleIcon size={18} />\n ) : (\n <ArrowsOutSimpleIcon size={18} />\n )}\n </Button>\n {externalUrl && (\n <a\n href={externalUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n aria-label=\"Abrir em nova aba\"\n className=\"p-1 rounded hover:bg-white/20\"\n >\n <ArrowSquareOutIcon size={18} />\n </a>\n )}\n </header>\n\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n <Text\n as=\"h2\"\n ref={titleRef}\n size=\"xl\"\n weight=\"bold\"\n color=\"text-text-950\"\n >\n {title}\n </Text>\n {article?.updatedAt && (\n <Text size=\"xs\" color=\"text-text-500\" className=\"mt-1\">\n Atualizado em {formatUpdatedAt(article.updatedAt)}\n </Text>\n )}\n\n <div className=\"mt-4\">\n {failed && (\n <Text size=\"sm\" role=\"alert\" color=\"text-error-700\">\n Não foi possível abrir o artigo.\n </Text>\n )}\n {!failed && !article && (\n <Text size=\"sm\" color=\"text-text-500\">\n Carregando…\n </Text>\n )}\n {article && <ArticleContent html={html} />}\n </div>\n </div>\n </div>\n );\n};\n","import { useEffect, useState, type ReactNode } from 'react';\nimport {\n FileIcon,\n FilePdfIcon,\n FileVideoIcon,\n XIcon,\n} from '@phosphor-icons/react';\nimport Text from '../Text/Text';\nimport Button from '../Button/Button';\nimport {\n SUPPORT_CHAT_ATTACHMENT_RULES,\n SUPPORT_CHAT_MAX_ATTACHMENTS,\n type SupportChatAttachment,\n} from '../../types/supportChat';\n\n/** Value of the `accept` attribute of the file picker */\nexport const SUPPORT_CHAT_ACCEPT = Object.keys(\n SUPPORT_CHAT_ATTACHMENT_RULES\n).join(',');\n\nconst isImage = (mimeType?: string) => Boolean(mimeType?.startsWith('image/'));\nconst isVideo = (mimeType?: string) => Boolean(mimeType?.startsWith('video/'));\n\nconst formatSize = (bytes: number) => `${Math.round(bytes / (1024 * 1024))} MB`;\n\n/**\n * Keep the files the backend accepts (type, size and count) and say why the\n * others were left out.\n * @param current - Files already picked\n * @param picked - Files just chosen\n * @returns The accepted files and the last problem found, if any\n */\nexport const pickAttachments = (current: File[], picked: File[]) => {\n const accepted = [...current];\n let problem: string | null = null;\n for (const file of picked) {\n const maxBytes = SUPPORT_CHAT_ATTACHMENT_RULES[file.type];\n if (!maxBytes) {\n problem =\n 'Envie imagens (JPG, PNG, GIF, WEBP), PDF ou vídeo (MP4, WEBM, MOV)';\n } else if (file.size > maxBytes) {\n problem = `${file.name} passa de ${formatSize(maxBytes)}`;\n } else if (accepted.length >= SUPPORT_CHAT_MAX_ATTACHMENTS) {\n problem = `Máximo de ${SUPPORT_CHAT_MAX_ATTACHMENTS} arquivos por mensagem`;\n } else {\n accepted.push(file);\n }\n }\n return { accepted, problem };\n};\n\nconst FileTypeIcon = ({ mimeType }: { mimeType?: string }) => {\n if (mimeType === 'application/pdf') return <FilePdfIcon size={18} />;\n if (isVideo(mimeType)) return <FileVideoIcon size={18} />;\n return <FileIcon size={18} />;\n};\n\n/** Files of a message: images and videos inline, other files as links */\nexport const AttachmentList = ({\n items,\n}: {\n items: SupportChatAttachment[];\n}) => (\n <div className=\"flex flex-wrap gap-2 first:mt-0 mt-1\">\n {items.map((item) => {\n if (isImage(item.mimeType)) {\n return (\n <a key={item.id} href={item.url} target=\"_blank\" rel=\"noreferrer\">\n <img\n src={item.url}\n alt={item.fileName}\n className=\"max-w-[200px] max-h-[200px] rounded-lg object-cover\"\n />\n </a>\n );\n }\n if (isVideo(item.mimeType)) {\n return (\n <video\n key={item.id}\n src={item.url}\n controls\n preload=\"metadata\"\n aria-label={item.fileName}\n className=\"max-w-[240px] max-h-[200px] rounded-lg bg-black\"\n />\n );\n }\n return (\n <a\n key={item.id}\n href={item.url}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"flex items-center gap-1 underline\"\n >\n <FileTypeIcon mimeType={item.mimeType} />\n <Text as=\"span\" size=\"sm\" color=\"\">\n {item.fileName}\n </Text>\n </a>\n );\n })}\n </div>\n);\n\n/** A file picked but not sent yet: thumbnail for images, name for the rest */\nexport const PendingAttachment = ({\n file,\n onRemove,\n}: {\n file: File;\n onRemove: () => void;\n}) => {\n const image = isImage(file.type);\n const [url, setUrl] = useState('');\n useEffect(() => {\n if (!image) return undefined;\n const objectUrl = URL.createObjectURL(file);\n setUrl(objectUrl);\n return () => URL.revokeObjectURL(objectUrl);\n }, [file, image]);\n\n return (\n <div className=\"relative\">\n {image ? (\n url && (\n <img\n src={url}\n alt={file.name}\n className=\"w-14 h-14 rounded-lg object-cover border border-border-100\"\n />\n )\n ) : (\n <div className=\"flex h-14 max-w-[160px] items-center gap-1 rounded-lg border border-border-100 px-2 text-text-700\">\n <FileTypeIcon mimeType={file.type} />\n <Text as=\"span\" size=\"xs\" className=\"truncate\">\n {file.name}\n </Text>\n </div>\n )}\n <Button\n variant=\"raw\"\n aria-label={`Remover ${file.name}`}\n onClick={onRemove}\n className=\"absolute -top-1 -right-1 w-5 h-5 rounded-full bg-text-700 text-white flex items-center justify-center\"\n >\n <XIcon size={12} />\n </Button>\n </div>\n );\n};\n\n/** http(s) addresses inside a message */\nconst URL_PATTERN = /https?:\\/\\/[^\\s<>\"]+/g;\n\n/** Punctuation that ends a sentence, not the address */\nconst TRAILING_PUNCTUATION = /[.,;:!?)\\]]+$/;\n\n/** A FAQ article page: `/artigo/<uuid>` */\nconst ARTICLE_PATH =\n /\\/artigo\\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;\n\n/**\n * Message text with its addresses as links. A link to a FAQ article opens in\n * the chat (when `onOpenArticle` is given); the others open a new tab.\n */\nexport const MessageText = ({\n text,\n onOpenArticle,\n}: {\n text: string;\n onOpenArticle?: (articleId: string) => void;\n}) => {\n const parts: ReactNode[] = [];\n let cursor = 0;\n for (const match of text.matchAll(URL_PATTERN)) {\n const raw = match[0];\n const url = raw.replace(TRAILING_PUNCTUATION, '');\n const start = match.index ?? 0;\n if (start > cursor) parts.push(text.slice(cursor, start));\n\n const articleId = onOpenArticle ? ARTICLE_PATH.exec(url)?.[1] : undefined;\n parts.push(\n <a\n key={`${start}-${url}`}\n href={url}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"underline break-all\"\n onClick={\n articleId\n ? (event) => {\n event.preventDefault();\n onOpenArticle?.(articleId);\n }\n : undefined\n }\n >\n {url}\n </a>\n );\n cursor = start + url.length;\n }\n if (cursor < text.length) parts.push(text.slice(cursor));\n\n return (\n <Text size=\"sm\" color=\"\" className=\"whitespace-pre-wrap\">\n {parts}\n </Text>\n );\n};\n","import { useMemo } from 'react';\nimport { SupportChatWidget } from './SupportChatWidget';\nimport { createGuestSupportChatAdapter } from './guestSupportChatAdapter';\nimport { useSupportFeatureFlag } from '../../hooks/useSupportFeatureFlag';\nimport { useAppStore } from '../../store/appStore';\nimport { useAuthStore } from '../../store/authStore';\nimport { SupportType, type SupportApiClient } from '../../types/support';\nimport type {\n SupportChatHttpClient,\n SupportChatTheme,\n} from '../../types/supportChat';\n\nexport interface NativeSupportChatProps {\n /** Client of the backend API (the app axios instance fits as it is) */\n apiClient: SupportApiClient & SupportChatHttpClient;\n /** App the chat runs in, sent with the ticket (e.g. \"aluno\", \"login\") */\n channel: string;\n /**\n * Extra condition of the app to show the chat, on top of the SUPPORT flag\n * (e.g. logged in and with the support module). Default: true.\n */\n enabled?: boolean;\n /**\n * Link of a FAQ article. Default: the FAQ address of the SUPPORT flag\n * (`<faqUrl>/artigo/<id>`).\n */\n faqArticleUrl?: (articleId: string) => string;\n /** Support WebSocket; without it the chat polls the REST API */\n wsUrl?: string;\n theme?: SupportChatTheme;\n}\n\n/**\n * Native support chat ready to drop in an app: reads the SUPPORT feature flag\n * of the institution (from the app store), the logged user (from the auth\n * store) and talks to the support API by itself.\n *\n * Shown only when the institution uses the native support with the internal\n * support and its chat switched on in the backoffice, once the flag is read,\n * and when `enabled`. With Zendesk it renders nothing (the app loads the\n * Zendesk widget). A logged user opens the ticket with their name and e-mail;\n * a visitor gives them to the AI on the first contact.\n *\n * @example\n * <NativeSupportChat apiClient={api} channel=\"aluno\" enabled={hasSupport} />\n */\nexport const NativeSupportChat = ({\n apiClient,\n channel,\n enabled = true,\n faqArticleUrl,\n wsUrl,\n theme,\n}: NativeSupportChatProps) => {\n const { institutionId } = useAppStore();\n const userName = useAuthStore(\n (state) => state.user?.name ?? state.sessionInfo?.userName\n );\n const userEmail = useAuthStore((state) => state.user?.email);\n const flag = useSupportFeatureFlag({ apiClient });\n const adapter = useMemo(\n () => createGuestSupportChatAdapter({ apiClient, wsUrl }),\n [apiClient, wsUrl]\n );\n\n const user =\n userName || userEmail ? { name: userName, email: userEmail } : undefined;\n\n return (\n <SupportChatWidget\n adapter={adapter}\n channel={channel}\n institutionId={institutionId || undefined}\n user={user}\n theme={theme}\n faqArticleUrl={faqArticleUrl ?? flag.faqArticleUrl}\n flags={{\n enabled: enabled && !flag.loading && flag.isNative && flag.chatEnabled,\n supportType: flag.isNative ? SupportType.NATIVE : SupportType.ZENDESK,\n }}\n />\n );\n};\n","import { isSupportTicketOrigin } from '../../types/support';\nimport {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_GUEST_SENDER_ID,\n SUPPORT_CHAT_STATUS,\n type SupportChatAdapter,\n type SupportChatAnswerApi,\n type SupportChatArticle,\n type SupportChatEvent,\n type SupportChatHttpClient,\n type SupportChatMessage,\n type SupportChatTicket,\n} from '../../types/supportChat';\n\nexport interface GuestSupportChatAdapterOptions {\n /** Client of the backend API (e.g. the app axios instance) */\n apiClient: SupportChatHttpClient;\n /**\n * Support WebSocket, including ?channel=support (e.g.\n * wss://bff-hml-chat.analyticaensino.com.br?channel=support).\n * Without it the adapter only polls the REST API.\n */\n wsUrl?: string;\n /** Polling interval while the socket is not connected, in ms */\n pollIntervalMs?: number;\n /**\n * localStorage key of the open conversation, kept so a reload resumes it.\n * Default: `analytica:support-chat`.\n */\n storageKey?: string;\n}\n\n/** Open conversation kept across page loads */\ninterface StoredSupportChatSession {\n ticketId: string;\n token: string;\n name: string;\n /** E-mail of the logged user who opened it; null for a visitor */\n userEmail: string | null;\n /** Opening text the chat does not show (image-only start) */\n hiddenId?: string;\n}\n\nconst DEFAULT_STORAGE_KEY = 'analytica:support-chat';\n\n/** The browser may block storage (private mode, policies): never throw */\nconst readStoredSession = (key: string): StoredSupportChatSession | null => {\n try {\n const raw = globalThis.localStorage?.getItem(key);\n if (!raw) return null;\n const session = JSON.parse(raw) as Partial<StoredSupportChatSession>;\n return session.ticketId && session.token\n ? (session as StoredSupportChatSession)\n : null;\n } catch {\n return null;\n }\n};\n\nconst writeStoredSession = (key: string, session: StoredSupportChatSession) => {\n try {\n globalThis.localStorage?.setItem(key, JSON.stringify(session));\n } catch {\n // Not kept: a reload starts a new conversation, as before\n }\n};\n\nconst clearStoredSession = (key: string) => {\n try {\n globalThis.localStorage?.removeItem(key);\n } catch {\n // Nothing kept to clear\n }\n};\n\nconst statusOf = (error: unknown) =>\n (error as { response?: { status?: number } } | null)?.response?.status;\n\nconst GUEST_TOKEN_HEADER = 'x-support-guest-token';\n\nconst senderNameFor = (role: string, requesterName: string) => {\n if (role === SUPPORT_CHAT_AUTHOR.AGENT) return 'Suporte';\n if (role === SUPPORT_CHAT_AUTHOR.AI) return 'Assistente';\n return requesterName;\n};\n\n/** REST answer → widget message */\nexport const fromSupportAnswer = (\n answer: SupportChatAnswerApi,\n requesterName: string\n): SupportChatMessage => ({\n id: answer.id,\n senderId:\n answer.authorType === SUPPORT_CHAT_AUTHOR.GUEST\n ? SUPPORT_CHAT_GUEST_SENDER_ID\n : answer.authorType.toLowerCase(),\n senderName: senderNameFor(answer.authorType, requesterName),\n senderPhoto: null,\n senderRole: answer.authorType,\n content: answer.answer,\n messageType: 'text',\n createdAt: answer.createdAt,\n attachments: answer.attachments ?? [],\n faq:\n answer.faqArticleId && answer.faqArticleTitle\n ? { id: answer.faqArticleId, title: answer.faqArticleTitle }\n : null,\n contactRequest: answer.contactRequest ?? null,\n});\n\n/**\n * Support chat adapter for visitors (no login), on top of the support API:\n * - POST /support/external opens the ticket and returns the guest token;\n * - GET/POST /support/guest/:id/answer read and send, with the token header;\n * - the chat WebSocket (?channel=support&roomId=&token=) pushes the events,\n * and REST polling covers the time the socket is down.\n */\nexport const createGuestSupportChatAdapter = ({\n apiClient,\n wsUrl,\n pollIntervalMs = 3000,\n storageKey = DEFAULT_STORAGE_KEY,\n}: GuestSupportChatAdapterOptions): SupportChatAdapter => {\n const sessions = new Map<string, { token: string; name: string }>();\n /**\n * Ticket opening text written by the adapter (an image-only start still\n * needs a description). It is the ticket description for the agents, not\n * something the visitor typed, so the chat does not show it.\n */\n const hiddenIds = new Set<string>();\n const isVisible = (message: SupportChatMessage) => !hiddenIds.has(message.id);\n\n const headersFor = (ticketId: string) => ({\n [GUEST_TOKEN_HEADER]: sessions.get(ticketId)?.token ?? '',\n });\n const nameFor = (ticketId: string) =>\n sessions.get(ticketId)?.name ?? 'Visitante';\n\n const listMessages = async (ticketId: string) => {\n const { data } = await apiClient.get<{ data: SupportChatAnswerApi[] }>(\n `/support/guest/${ticketId}/answer`,\n { headers: headersFor(ticketId) }\n );\n return data.data\n .map((answer) => fromSupportAnswer(answer, nameFor(ticketId)))\n .filter(isVisible);\n };\n\n /**\n * Upload each file straight to the storage: ask the backend for a signed\n * URL, then PUT the file there. Returns what the message needs.\n */\n const uploadFiles = (ticketId: string, files: File[]) =>\n Promise.all(\n files.map(async (file) => {\n const { data } = await apiClient.post<{\n data: { uploadUrl: string; key: string; contentType: string };\n }>(\n `/support/guest/${ticketId}/upload-url`,\n { fileName: file.name, fileSize: file.size, mimeType: file.type },\n { headers: headersFor(ticketId) }\n );\n const { uploadUrl, key, contentType } = data.data;\n const response = await fetch(uploadUrl, {\n method: 'PUT',\n headers: { 'Content-Type': contentType },\n body: file,\n });\n if (!response.ok) {\n throw new Error(`Não foi possível enviar ${file.name}`);\n }\n return {\n key,\n fileName: file.name,\n mimeType: contentType,\n size: file.size,\n };\n })\n );\n\n const sendMessage = async (\n ticketId: string,\n content: string,\n files: File[] = []\n ) => {\n const attachments = await uploadFiles(ticketId, files);\n const { data } = await apiClient.post<{ data: SupportChatAnswerApi }>(\n `/support/guest/${ticketId}/answer`,\n { answer: content, attachments },\n { headers: headersFor(ticketId) }\n );\n return fromSupportAnswer(data.data, nameFor(ticketId));\n };\n\n return {\n async startConversation({\n message,\n channel,\n institutionId,\n requesterName,\n email,\n files = [],\n }) {\n // The ticket needs a description; an image-only start uses the names\n const description = message || files.map((file) => file.name).join(', ');\n const { data } = await apiClient.post<{\n data: { id: string; guestToken: string };\n }>('/support/external', {\n description,\n ...(institutionId ? { institutionId } : {}),\n ...(requesterName ? { requesterName } : {}),\n ...(email ? { email } : {}),\n // The app the chat runs in, when it is one the API knows\n ...(isSupportTicketOrigin(channel) ? { origin: channel } : {}),\n });\n const ticketId = data.data.id;\n const name = requesterName || 'Visitante';\n sessions.set(ticketId, { token: data.data.guestToken, name });\n\n // The first message is saved with the ticket; read it back for its id.\n // /support/external takes no files, so they go as the next message.\n let messages = await listMessages(ticketId);\n let hiddenId: string | undefined;\n if (!message && messages[0]) {\n hiddenId = messages[0].id;\n hiddenIds.add(hiddenId);\n messages = messages.slice(1);\n }\n writeStoredSession(storageKey, {\n ticketId,\n token: data.data.guestToken,\n name,\n userEmail: email ?? null,\n hiddenId,\n });\n if (files.length) messages.push(await sendMessage(ticketId, '', files));\n return {\n ticket: {\n id: ticketId,\n status: SUPPORT_CHAT_STATUS.ABERTO,\n handlerType: null,\n problemType: null,\n },\n messages,\n };\n },\n\n sendMessage,\n\n async getArticle(institutionId, articleId) {\n const { data } = await apiClient.get<{\n data: { article: SupportChatArticle };\n }>(`/help-center/${institutionId}/articles/${articleId}`);\n const { id, title, body, updatedAt } = data.data.article;\n return { id, title, body, updatedAt: updatedAt ?? null };\n },\n\n async updateContact(ticketId, contact) {\n if (!apiClient.put) {\n throw new Error('O cliente da API não envia PUT');\n }\n await apiClient.put(`/support/guest/${ticketId}`, contact, {\n headers: headersFor(ticketId),\n });\n const session = sessions.get(ticketId);\n if (session && contact.requesterName) {\n session.name = contact.requesterName;\n const stored = readStoredSession(storageKey);\n if (stored?.ticketId === ticketId) {\n writeStoredSession(storageKey, {\n ...stored,\n name: contact.requesterName,\n });\n }\n }\n },\n\n async restoreConversation({ email }) {\n const stored = readStoredSession(storageKey);\n if (!stored) return null;\n // Another account on this browser: that conversation is not theirs\n if (stored.userEmail !== (email ?? null)) {\n clearStoredSession(storageKey);\n return null;\n }\n\n const { ticketId } = stored;\n sessions.set(ticketId, { token: stored.token, name: stored.name });\n if (stored.hiddenId) hiddenIds.add(stored.hiddenId);\n try {\n const { data } = await apiClient.get<{ data: SupportChatTicket }>(\n `/support/guest/${ticketId}`,\n { headers: headersFor(ticketId) }\n );\n if (data.data.status === SUPPORT_CHAT_STATUS.FECHADO) {\n clearStoredSession(storageKey);\n return null;\n }\n return { ticket: data.data, messages: await listMessages(ticketId) };\n } catch (error) {\n // Ticket gone or token expired: it cannot be resumed. Any other\n // failure (offline) keeps it for the next load.\n if (statusOf(error) === 404) clearStoredSession(storageKey);\n sessions.delete(ticketId);\n return null;\n }\n },\n\n forgetConversation() {\n clearStoredSession(storageKey);\n },\n\n listMessages,\n\n subscribe(ticketId, listener) {\n // id → what the chat shows besides the text; a message seen again with\n // more of it (a stale WebSocket history may lack contactRequest/faq) is\n // delivered again so the chat replaces it\n const seen = new Map<string, string>();\n const extrasOf = (message: SupportChatMessage) =>\n JSON.stringify([message.contactRequest ?? null, message.faq ?? null]);\n const NO_EXTRAS = JSON.stringify([null, null]);\n let socket: WebSocket | null = null;\n let pollTimer: ReturnType<typeof setInterval> | null = null;\n let active = true;\n\n const deliver = (message: SupportChatMessage) => {\n if (!isVisible(message)) return;\n const extras = extrasOf(message);\n const previous = seen.get(message.id);\n // Same as before, or a poorer copy: nothing new to show\n if (previous === extras || (previous && extras === NO_EXTRAS)) return;\n seen.set(message.id, extras);\n listener({ type: 'new_message', payload: { message } });\n };\n\n const poll = async () => {\n try {\n (await listMessages(ticketId)).forEach(deliver);\n } catch {\n // Next tick tries again\n }\n };\n const startPolling = () => {\n if (pollTimer || !active) return;\n void poll();\n pollTimer = setInterval(poll, pollIntervalMs);\n };\n const stopPolling = () => {\n if (pollTimer) clearInterval(pollTimer);\n pollTimer = null;\n };\n\n const handleFrame = (raw: string) => {\n let event: { type?: string; payload?: Record<string, unknown> };\n try {\n event = JSON.parse(raw);\n } catch {\n return;\n }\n const payload = event.payload ?? {};\n if (event.type === 'history') {\n (payload.messages as SupportChatMessage[]).forEach((message) =>\n deliver({ ...message, attachments: message.attachments ?? [] })\n );\n } else if (event.type === 'new_message') {\n const message = payload.message as SupportChatMessage;\n deliver({ ...message, attachments: message.attachments ?? [] });\n } else if (\n event.type === 'message_updated' ||\n event.type === 'ticket_updated'\n ) {\n listener(event as SupportChatEvent);\n } else if (event.type === 'error') {\n listener({\n type: 'error',\n payload: {\n message: String(payload.message_text ?? payload.message ?? ''),\n },\n });\n }\n };\n\n const token = sessions.get(ticketId)?.token;\n if (wsUrl && token) {\n const url = new URL(wsUrl);\n url.searchParams.set('channel', 'support');\n url.searchParams.set('roomId', ticketId);\n url.searchParams.set('token', token);\n socket = new WebSocket(url.toString());\n socket.onopen = () => {\n stopPolling();\n socket?.send(JSON.stringify({ type: 'getInitialData' }));\n // The REST API is the source of truth: a WebSocket deployed apart\n // from it may send a history without contactRequest/faq\n void poll();\n };\n socket.onmessage = (event) => handleFrame(String(event.data));\n socket.onclose = startPolling;\n socket.onerror = startPolling;\n } else {\n startPolling();\n }\n\n return () => {\n active = false;\n stopPolling();\n if (socket) {\n socket.onclose = null;\n socket.close();\n }\n };\n },\n };\n};\n","import {\n SUPPORT_CHAT_AUTHOR,\n SUPPORT_CHAT_GUEST_SENDER_ID,\n SUPPORT_CHAT_HANDLER,\n SUPPORT_CHAT_STATUS,\n type SupportChatAdapter,\n type SupportChatEvent,\n type SupportChatMessage,\n type SupportChatTicket,\n} from '../../types/supportChat';\n\nexport interface MockSupportChatAdapterOptions {\n /** Latency of every call, in ms (default 300) */\n delayMs?: number;\n /** Delay of the automatic agent reply, in ms (default 1500) */\n replyDelayMs?: number;\n /** Text of the agent reply; null disables it */\n replyText?: string | null;\n}\n\nconst DEFAULT_REPLY =\n 'Olá! Recebemos sua mensagem e já vamos te ajudar. Este é um atendimento de demonstração.';\n\n/**\n * In-memory adapter for the support chat widget. It mirrors the backend\n * contract (statuses, authors, events) so the widget can be built and tested\n * before the real API + WebSocket adapter exists.\n */\nexport const createMockSupportChatAdapter = ({\n delayMs = 300,\n replyDelayMs = 1500,\n replyText = DEFAULT_REPLY,\n}: MockSupportChatAdapterOptions = {}): SupportChatAdapter => {\n const tickets = new Map<string, SupportChatTicket>();\n const messages = new Map<string, SupportChatMessage[]>();\n const requesters = new Map<string, { name: string; logged: boolean }>();\n const listeners = new Map<string, Set<(event: SupportChatEvent) => void>>();\n let sequence = 0;\n\n const wait = () => new Promise((resolve) => setTimeout(resolve, delayMs));\n const nextId = (prefix: string) => `${prefix}-${++sequence}`;\n\n const emit = (ticketId: string, event: SupportChatEvent) => {\n listeners.get(ticketId)?.forEach((listener) => listener(event));\n };\n\n const append = (ticketId: string, message: SupportChatMessage) => {\n messages.set(ticketId, [...(messages.get(ticketId) ?? []), message]);\n };\n\n const toAttachments = (files: File[]) =>\n files.map((file) => ({\n id: nextId('file'),\n url: URL.createObjectURL(file),\n fileName: file.name,\n mimeType: file.type,\n size: file.size,\n }));\n\n const buildRequesterMessage = (\n ticketId: string,\n content: string,\n files: File[] = []\n ): SupportChatMessage => {\n const requester = requesters.get(ticketId);\n return {\n id: nextId('msg'),\n senderId: requester?.logged ? 'user' : SUPPORT_CHAT_GUEST_SENDER_ID,\n senderName: requester?.name ?? 'Visitante',\n senderPhoto: null,\n senderRole: requester?.logged\n ? SUPPORT_CHAT_AUTHOR.USER\n : SUPPORT_CHAT_AUTHOR.GUEST,\n content,\n messageType: 'text',\n createdAt: new Date().toISOString(),\n attachments: toAttachments(files),\n };\n };\n\n const scheduleReply = (ticketId: string) => {\n if (replyText === null) return;\n setTimeout(() => {\n const ticket = tickets.get(ticketId);\n if (!ticket) return;\n\n const reply: SupportChatMessage = {\n id: nextId('msg'),\n senderId: 'agent',\n senderName: 'Suporte',\n senderPhoto: null,\n senderRole: SUPPORT_CHAT_AUTHOR.AGENT,\n content: replyText,\n messageType: 'text',\n createdAt: new Date().toISOString(),\n attachments: [],\n };\n append(ticketId, reply);\n\n const updated: SupportChatTicket = {\n ...ticket,\n status: SUPPORT_CHAT_STATUS.EM_ANDAMENTO,\n handlerType: SUPPORT_CHAT_HANDLER.HUMANO,\n };\n tickets.set(ticketId, updated);\n\n emit(ticketId, { type: 'new_message', payload: { message: reply } });\n emit(ticketId, {\n type: 'ticket_updated',\n payload: {\n ticketId,\n status: updated.status,\n handlerType: updated.handlerType,\n problemType: updated.problemType,\n },\n });\n }, replyDelayMs);\n };\n\n return {\n async getArticle(_institutionId, articleId) {\n await wait();\n return {\n id: articleId,\n title: 'Como gerar e localizar simulados',\n body:\n '<p>Monte um simulado com as matérias que quiser e acompanhe o resultado. 📝</p>' +\n '<h2>Passo a passo</h2>' +\n '<ol><li><p>No menu, abra <strong>Simulados</strong>.</p></li>' +\n '<li><p>Clique em <strong>Gerar simulado</strong> e escolha as áreas.</p></li>' +\n '<li><p>Responda e veja o resultado em <strong>Histórico</strong>.</p></li></ol>' +\n '<p><a href=\"https://www.youtube.com/watch?v=dQw4w9WgXcQ\">Assista ao vídeo do passo a passo</a></p>' +\n '<blockquote><p>💡 Dica: refaça as questões que errou.</p></blockquote>',\n updatedAt: '2026-10-21T12:00:00.000Z',\n };\n },\n\n async startConversation({ message, requesterName, email, files }) {\n await wait();\n const ticket: SupportChatTicket = {\n id: nextId('ticket'),\n status: SUPPORT_CHAT_STATUS.ABERTO,\n handlerType: null,\n problemType: null,\n };\n tickets.set(ticket.id, ticket);\n requesters.set(ticket.id, {\n name: requesterName || 'Visitante',\n logged: Boolean(email),\n });\n\n const first = buildRequesterMessage(ticket.id, message, files);\n append(ticket.id, first);\n scheduleReply(ticket.id);\n return { ticket, messages: [first] };\n },\n\n async sendMessage(ticketId, content, files) {\n await wait();\n const ticket = tickets.get(ticketId);\n if (!ticket) throw new Error('Atendimento não encontrado');\n if (ticket.status === SUPPORT_CHAT_STATUS.FECHADO) {\n throw new Error('Este atendimento já foi encerrado');\n }\n\n const message = buildRequesterMessage(ticketId, content, files);\n append(ticketId, message);\n scheduleReply(ticketId);\n return message;\n },\n\n async listMessages(ticketId) {\n await wait();\n return [...(messages.get(ticketId) ?? [])];\n },\n\n subscribe(ticketId, listener) {\n const set = listeners.get(ticketId) ?? new Set();\n set.add(listener);\n listeners.set(ticketId, set);\n return () => {\n set.delete(listener);\n };\n },\n };\n};\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useState","XIcon","useState","useState","useEffect","useRef","useState","jsx","jsxs","useState","useRef","useEffect","title","useEffect","useState","jsx","jsxs","useState","useEffect","jsx","jsxs","useState","useRef","useCallback","useEffect","XIcon","useMemo","jsx","useMemo"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SUPPORT_CHAT_REPLY_MODE
3
- } from "./chunk-7ZB3YHF2.mjs";
3
+ } from "./chunk-C6LC5LD5.mjs";
4
4
  import {
5
5
  TextArea_default
6
6
  } from "./chunk-6A2IK5NJ.mjs";
@@ -235,4 +235,4 @@ var SupportChatComposer_default = SupportChatComposer;
235
235
  export {
236
236
  SupportChatComposer_default
237
237
  };
238
- //# sourceMappingURL=chunk-I4QGQMXI.mjs.map
238
+ //# sourceMappingURL=chunk-EXYXZFNK.mjs.map
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkPOT3ALY3js = require('./chunk-POT3ALY3.js');
3
+ var _chunk5DINXU6Wjs = require('./chunk-5DINXU6W.js');
4
4
 
5
5
 
6
6
  var _chunkBSMYGZ5Qjs = require('./chunk-BSMYGZ5Q.js');
@@ -35,12 +35,12 @@ var filterQuickReplies = (replies, query) => {
35
35
  );
36
36
  };
37
37
  var MODE_OPTIONS = [
38
- { mode: _chunkPOT3ALY3js.SUPPORT_CHAT_REPLY_MODE.USER, label: "Resposta ao usu\xE1rio" },
39
- { mode: _chunkPOT3ALY3js.SUPPORT_CHAT_REPLY_MODE.INTERNAL_NOTE, label: "Nota interna" }
38
+ { mode: _chunk5DINXU6Wjs.SUPPORT_CHAT_REPLY_MODE.USER, label: "Resposta ao usu\xE1rio" },
39
+ { mode: _chunk5DINXU6Wjs.SUPPORT_CHAT_REPLY_MODE.INTERNAL_NOTE, label: "Nota interna" }
40
40
  ];
41
41
  var SupportChatComposer = ({
42
42
  onSend,
43
- defaultMode = _chunkPOT3ALY3js.SUPPORT_CHAT_REPLY_MODE.USER,
43
+ defaultMode = _chunk5DINXU6Wjs.SUPPORT_CHAT_REPLY_MODE.USER,
44
44
  onModeChange,
45
45
  onAttachImages,
46
46
  quickReplies,
@@ -54,7 +54,7 @@ var SupportChatComposer = ({
54
54
  const [quickMenuOpen, setQuickMenuOpen] = _react.useState.call(void 0, false);
55
55
  const fileInputRef = _react.useRef.call(void 0, null);
56
56
  const hasQuickReplies = Boolean(_optionalChain([quickReplies, 'optionalAccess', _ => _.length]));
57
- const isNote = mode === _chunkPOT3ALY3js.SUPPORT_CHAT_REPLY_MODE.INTERNAL_NOTE;
57
+ const isNote = mode === _chunk5DINXU6Wjs.SUPPORT_CHAT_REPLY_MODE.INTERNAL_NOTE;
58
58
  const slashQuery = hasQuickReplies && text.startsWith("/") ? text.slice(1) : null;
59
59
  const menuOpen = !disabled && (quickMenuOpen || slashQuery !== null);
60
60
  const visibleReplies = menuOpen ? filterQuickReplies(_nullishCoalesce(quickReplies, () => ( [])), _nullishCoalesce(slashQuery, () => ( ""))) : [];
@@ -235,4 +235,4 @@ var SupportChatComposer_default = SupportChatComposer;
235
235
 
236
236
 
237
237
  exports.SupportChatComposer_default = SupportChatComposer_default;
238
- //# sourceMappingURL=chunk-FU23WOTY.js.map
238
+ //# sourceMappingURL=chunk-HSV5VZ4Z.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-FU23WOTY.js","../src/components/SupportChat/SupportChatComposer.tsx"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACA;ACtBA,8BAAuE;AACvE,6DAA0B;AAC1B,mFAAqC;AACrC,2DAAyB;AAoKf,+CAAA;AAzHV,IAAM,oBAAA,EAAsB,EAAA;AAG5B,IAAM,mBAAA,EAAqB,CACzB,OAAA,EACA,KAAA,EAAA,GACwB;AACxB,EAAA,MAAM,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,CAAA,CAAE,WAAA,CAAY,CAAA;AACtC,EAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,OAAA;AAClB,EAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,IACb,CAAC,KAAA,EAAA,GACC,KAAA,CAAM,KAAA,CAAM,WAAA,CAAY,CAAA,CAAE,QAAA,CAAS,IAAI,EAAA,GACvC,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,CAAA,CAAE,QAAA,CAAS,IAAI;AAAA,EAC1C,CAAA;AACF,CAAA;AAEA,IAAM,aAAA,EAGD;AAAA,EACH,EAAE,IAAA,EAAM,wCAAA,CAAwB,IAAA,EAAM,KAAA,EAAO,yBAAsB,CAAA;AAAA,EACnE,EAAE,IAAA,EAAM,wCAAA,CAAwB,aAAA,EAAe,KAAA,EAAO,eAAe;AACvE,CAAA;AAiBA,IAAM,oBAAA,EAAsB,CAAC;AAAA,EAC3B,MAAA;AAAA,EACA,YAAA,EAAc,wCAAA,CAAwB,IAAA;AAAA,EACtC,YAAA;AAAA,EACA,cAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA,EAAW,KAAA;AAAA,EACX,WAAA;AAAA,EACA;AACF,CAAA,EAAA,GAAgC;AAC9B,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,EAAA,EAAI,6BAAA,WAA0C,CAAA;AAClE,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,EAAA,EAAI,6BAAA,EAAW,CAAA;AACnC,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,EAAA,EAAI,6BAAA,KAAc,CAAA;AACxD,EAAA,MAAM,aAAA,EAAe,2BAAA,IAA6B,CAAA;AAElD,EAAA,MAAM,gBAAA,EAAkB,OAAA,iBAAQ,YAAA,2BAAc,QAAM,CAAA;AACpD,EAAA,MAAM,OAAA,EAAS,KAAA,IAAS,wCAAA,CAAwB,aAAA;AAEhD,EAAA,MAAM,WAAA,EACJ,gBAAA,GAAmB,IAAA,CAAK,UAAA,CAAW,GAAG,EAAA,EAAI,IAAA,CAAK,KAAA,CAAM,CAAC,EAAA,EAAI,IAAA;AAC5D,EAAA,MAAM,SAAA,EAAW,CAAC,SAAA,GAAA,CAAa,cAAA,GAAiB,WAAA,IAAe,IAAA,CAAA;AAC/D,EAAA,MAAM,eAAA,EAAiB,SAAA,EACnB,kBAAA,kBAAmB,YAAA,UAAgB,CAAC,GAAA,mBAAG,UAAA,UAAc,IAAE,EAAA,EACvD,CAAC,CAAA;AACL,EAAA,MAAM,iBAAA,mBACJ,WAAA,UAAA,CACC,gBAAA,EACG,gEAAA,EACA,4BAAA,GAAA;AAEN,EAAA,MAAM,eAAA,EAAiB,CAAC,KAAA,EAAA,GAA6B;AACnD,IAAA,OAAA,CAAQ,KAAA,CAAM,IAAI,CAAA;AAClB,IAAA,gBAAA,CAAiB,KAAK,CAAA;AAAA,EACxB,CAAA;AAEA,EAAA,MAAM,QAAA,EAAU,CAAC,SAAA,GAAY,IAAA,CAAK,IAAA,CAAK,CAAA,CAAE,OAAA,EAAS,CAAA;AAElD,EAAA,MAAM,WAAA,EAAa,CAAC,IAAA,EAAA,GAA+B;AACjD,IAAA,GAAA,CAAI,KAAA,IAAS,IAAA,EAAM,MAAA;AACnB,IAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,oBAAA,YAAA,0BAAA,CAAe,IAAI,GAAA;AAAA,EACrB,CAAA;AAEA,EAAA,MAAM,KAAA,EAAO,CAAA,EAAA,GAAM;AACjB,IAAA,GAAA,CAAI,CAAC,OAAA,EAAS,MAAA;AACd,IAAA,MAAA,CAAO,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,CAAK,CAAA,EAAG,KAAK,CAAC,CAAA;AAClC,IAAA,OAAA,CAAQ,EAAE,CAAA;AAAA,EACZ,CAAA;AAEA,EAAA,MAAM,cAAA,EAAgB,CAAC,KAAA,EAAA,GAA8C;AACnE,IAAA,GAAA,CAAI,KAAA,CAAM,IAAA,IAAQ,SAAA,GAAY,QAAA,EAAU;AACtC,MAAA,KAAA,CAAM,cAAA,CAAe,CAAA;AACrB,MAAA,gBAAA,CAAiB,KAAK,CAAA;AACtB,MAAA,GAAA,CAAI,WAAA,IAAe,IAAA,EAAM,OAAA,CAAQ,EAAE,CAAA;AACnC,MAAA,MAAA;AAAA,IACF;AAEA,IAAA,GAAA,CACE,KAAA,CAAM,IAAA,IAAQ,QAAA,GACd,CAAC,KAAA,CAAM,SAAA,GACP,CAAC,KAAA,CAAM,WAAA,CAAY,WAAA,EACnB;AACA,MAAA,KAAA,CAAM,cAAA,CAAe,CAAA;AACrB,MAAA,IAAA,CAAK,CAAA;AAAA,IACP;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,YAAA,EAAc,CAAC,KAAA,EAAA,GAAyC;AAC5D,IAAA,MAAM,MAAA,EAAQ,KAAA,CAAM,IAAA,kBAAK,KAAA,CAAM,MAAA,CAAO,KAAA,UAAS,CAAC,GAAC,CAAA;AAEjD,IAAA,KAAA,CAAM,MAAA,CAAO,MAAA,EAAQ,EAAA;AACrB,IAAA,GAAA,CAAI,KAAA,CAAM,OAAA,EAAS,CAAA,kBAAG,cAAA,0BAAA,CAAiB,KAAK,GAAA;AAAA,EAC9C,CAAA;AAEA,EAAA,uBACE,8BAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,iCAAA,4BAAG,EAA8B,SAAS,CAAA;AAAA,MACrD,aAAA,EAAY,uBAAA;AAAA,MAEZ,QAAA,EAAA;AAAA,wBAAA,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sBAAA,EACZ,QAAA,EAAA,YAAA,CAAa,GAAA,CAAI,CAAC,MAAA,EAAA,mBACjB,6BAAA;AAAA,UAAC,8BAAA;AAAA,UAAA;AAAA,YAEC,QAAA,EAAU,KAAA,IAAS,MAAA,CAAO,IAAA;AAAA,YAC1B,cAAA,EAAc,KAAA,IAAS,MAAA,CAAO,IAAA;AAAA,YAC9B,OAAA,EAAS,CAAA,EAAA,GAAM,UAAA,CAAW,MAAA,CAAO,IAAI,CAAA;AAAA,YAEpC,QAAA,EAAA,MAAA,CAAO;AAAA,UAAA,CAAA;AAAA,UALH,MAAA,CAAO;AAAA,QAMd,CACD,EAAA,CACH,CAAA;AAAA,wBAEA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,UAAA,EACZ,QAAA,EAAA;AAAA,UAAA,SAAA,mBACC,6BAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,MAAA;AAAA,cACL,YAAA,EAAW,sBAAA;AAAA,cACX,aAAA,EAAY,qCAAA;AAAA,cACZ,SAAA,EAAU,8JAAA;AAAA,cAET,QAAA,EAAA,cAAA,CAAe,OAAA,IAAW,EAAA,kBACzB,6BAAA,6BAAC,EAAA,EAAK,IAAA,EAAK,IAAA,EAAK,KAAA,EAAM,eAAA,EAAgB,SAAA,EAAU,WAAA,EAAY,QAAA,EAAA,wCAAA,CAE5D,EAAA,EAEA,cAAA,CAAe,GAAA,CAAI,CAAC,KAAA,EAAA,mBAClB,8BAAA;AAAA,gBAAC,+BAAA;AAAA,gBAAA;AAAA,kBAEC,OAAA,EAAQ,KAAA;AAAA,kBACR,IAAA,EAAK,UAAA;AAAA,kBACL,OAAA,EAAS,CAAA,EAAA,GAAM,cAAA,CAAe,KAAK,CAAA;AAAA,kBACnC,SAAA,EAAU,yFAAA;AAAA,kBAEV,QAAA,EAAA;AAAA,oCAAA,6BAAA,6BAAC,EAAA,EAAK,IAAA,EAAK,IAAA,EAAK,MAAA,EAAO,UAAA,EAAW,KAAA,EAAM,eAAA,EACrC,QAAA,EAAA,KAAA,CAAM,MAAA,CACT,CAAA;AAAA,oCACA,6BAAA;AAAA,sBAAC,6BAAA;AAAA,sBAAA;AAAA,wBACC,IAAA,EAAK,IAAA;AAAA,wBACL,KAAA,EAAM,eAAA;AAAA,wBACN,SAAA,EAAU,cAAA;AAAA,wBAET,QAAA,EAAA,KAAA,CAAM;AAAA,sBAAA;AAAA,oBACT;AAAA,kBAAA;AAAA,gBAAA,CAAA;AAAA,gBAfK,KAAA,CAAM;AAAA,cAgBb,CACD;AAAA,YAAA;AAAA,UAEL,CAAA;AAAA,0BAEF,6BAAA;AAAA,YAAC,iCAAA;AAAA,YAAA;AAAA,cACC,YAAA,EAAW,UAAA;AAAA,cACX,WAAA,EACE,OAAA,EAAS,uCAAA,EAAoC,gBAAA;AAAA,cAE/C,KAAA,EAAO,IAAA;AAAA,cACP,QAAA,EAAU,CAAC,KAAA,EAAA,GAAU,OAAA,CAAQ,KAAA,CAAM,MAAA,CAAO,KAAK,CAAA;AAAA,cAC/C,SAAA,EAAW,aAAA;AAAA,cACX,QAAA;AAAA,cACA,UAAA,EAAU,IAAA;AAAA,cAGV,SAAA,EAAW,mBAAA;AAAA,cACX,SAAA,EAAW,iCAAA;AAAA,gBACT,YAAA;AAAA;AAAA,gBAEA,OAAA,GAAU;AAAA,cACZ,CAAA;AAAA,cACA,WAAA,EAAW;AAAA,YAAA;AAAA,UACb;AAAA,QAAA,EAAA,CACF,CAAA;AAAA,wBAEA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mDAAA,EACb,QAAA,EAAA;AAAA,0BAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mCAAA,EACZ,QAAA,EAAA;AAAA,YAAA,eAAA,mBACC,8BAAA,oBAAA,EAAA,EACE,QAAA,EAAA;AAAA,8BAAA,6BAAA;AAAA,gBAAC,OAAA;AAAA,gBAAA;AAAA,kBACC,GAAA,EAAK,YAAA;AAAA,kBACL,IAAA,EAAK,MAAA;AAAA,kBACL,MAAA,EAAO,SAAA;AAAA,kBACP,QAAA,EAAQ,IAAA;AAAA,kBACR,MAAA,EAAM,IAAA;AAAA,kBACN,aAAA,EAAY,kCAAA;AAAA,kBACZ,QAAA,EAAU;AAAA,gBAAA;AAAA,cACZ,CAAA;AAAA,8BACA,6BAAA;AAAA,gBAAC,mCAAA;AAAA,gBAAA;AAAA,kBACC,IAAA,kBAAM,6BAAA,gBAAC,EAAA,EAAU,IAAA,EAAM,GAAA,CAAI,CAAA;AAAA,kBAC3B,IAAA,EAAK,IAAA;AAAA,kBACL,YAAA,EAAW,eAAA;AAAA,kBACX,QAAA;AAAA,kBACA,OAAA,EAAS,CAAA,EAAA,mBAAM,YAAA,qBAAa,OAAA,6BAAS,KAAA,mBAAM;AAAA,gBAAA;AAAA,cAC7C;AAAA,YAAA,EAAA,CACF,CAAA;AAAA,YAED,gBAAA,mBACC,6BAAA;AAAA,cAAC,+BAAA;AAAA,cAAA;AAAA,gBACC,OAAA,EAAQ,MAAA;AAAA,gBACR,MAAA,EAAO,WAAA;AAAA,gBACP,IAAA,EAAK,OAAA;AAAA,gBACL,QAAA,kBAAU,6BAAA,sCAAC,EAAA,EAAqB,IAAA,EAAM,GAAA,CAAI,CAAA;AAAA,gBAC1C,eAAA,EAAe,QAAA;AAAA,gBACf,QAAA;AAAA,gBACA,OAAA,EAAS,CAAA,EAAA,GAAM,gBAAA,CAAiB,CAAC,IAAA,EAAA,GAAS,CAAC,IAAI,CAAA;AAAA,gBAChD,QAAA,EAAA;AAAA,cAAA;AAAA,YAED,CAAA;AAAA,YAED,UAAA,mBACC,6BAAA;AAAA,cAAC,+BAAA;AAAA,cAAA;AAAA,gBACC,OAAA,EAAQ,MAAA;AAAA,gBACR,MAAA,EAAO,WAAA;AAAA,gBACP,IAAA,EAAK,OAAA;AAAA,gBACL,QAAA,kBAAU,6BAAA,cAAC,EAAA,EAAS,IAAA,EAAM,GAAA,CAAI,CAAA;AAAA,gBAC9B,QAAA;AAAA,gBACA,OAAA,EAAS,SAAA;AAAA,gBACV,QAAA,EAAA;AAAA,cAAA;AAAA,YAED;AAAA,UAAA,EAAA,CAEJ,CAAA;AAAA,0BAEA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,4BAAA,6BAAA,6BAAC,EAAA,EAAK,IAAA,EAAK,IAAA,EAAK,KAAA,EAAM,eAAA,EAAgB,QAAA,EAAA,4CAAA,CAEtC,CAAA;AAAA,4BACA,6BAAA;AAAA,cAAC,+BAAA;AAAA,cAAA;AAAA,gBACC,OAAA,EAAQ,OAAA;AAAA,gBACR,MAAA,EAAO,SAAA;AAAA,gBACP,IAAA,EAAK,QAAA;AAAA,gBACL,OAAA,EAAS,IAAA;AAAA,gBACT,QAAA,EAAU,CAAC,OAAA;AAAA,gBACZ,QAAA,EAAA;AAAA,cAAA;AAAA,YAED;AAAA,UAAA,EAAA,CACF;AAAA,QAAA,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,CAAA;AAEJ,CAAA;AAEA,IAAO,4BAAA,EAAQ,mBAAA;ADvEf;AACA;AACE;AACF,kEAAC","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-FU23WOTY.js","sourcesContent":[null,"import { useRef, useState, type ChangeEvent, type KeyboardEvent } from 'react';\nimport { ImageIcon } from '@phosphor-icons/react/dist/csr/Image';\nimport { ChatTeardropTextIcon } from '@phosphor-icons/react/dist/csr/ChatTeardropText';\nimport { LinkIcon } from '@phosphor-icons/react/dist/csr/Link';\nimport Chips from '../Chips/Chips';\nimport TextArea from '../TextArea/TextArea';\nimport Button from '../Button/Button';\nimport IconButton from '../IconButton/IconButton';\nimport Text from '../Text/Text';\nimport {\n SUPPORT_CHAT_REPLY_MODE,\n type SupportChatReplyMode,\n} from '../../types/supportChat';\nimport type { SupportQuickReply } from '../../types/supportConversation';\nimport { cn } from '../../utils/utils';\n\n/**\n * Props of the SupportChatComposer component\n */\nexport interface SupportChatComposerProps {\n /** Called with the text and the chosen mode; the field is cleared afterwards */\n onSend: (data: { text: string; mode: SupportChatReplyMode }) => void;\n /** Mode selected when the composer mounts (default: reply to the user) */\n defaultMode?: SupportChatReplyMode;\n /** Called when the agent switches between reply and internal note */\n onModeChange?: (mode: SupportChatReplyMode) => void;\n /** Images picked with the image button; without it the button is hidden */\n onAttachImages?: (files: File[]) => void;\n /** Blocks sending (e.g. while a reply is being sent) */\n disabled?: boolean;\n /**\n * Saved replies. With them the \"Respostas rápidas\" button shows up and\n * typing \"/\" at the start of the field opens the same menu, filtered by\n * what follows the slash. Picking one replaces the text.\n */\n quickReplies?: SupportQuickReply[];\n /** Opens the FAQ search; without it the \"FAQ\" button is hidden */\n onOpenFaq?: () => void;\n /** Placeholder of the text field (mentions \"/\" when there are quick replies) */\n placeholder?: string;\n /** Extra classes for the root (it takes the full width of its parent) */\n className?: string;\n}\n\n/** Mode chips, in display order */\n/** Starting height of the text field, in px: a single line */\nconst COMPOSER_MIN_HEIGHT = 48;\n\n/** Quick replies whose title or text contains the query (case-insensitive) */\nconst filterQuickReplies = (\n replies: SupportQuickReply[],\n query: string\n): SupportQuickReply[] => {\n const term = query.trim().toLowerCase();\n if (!term) return replies;\n return replies.filter(\n (reply) =>\n reply.title.toLowerCase().includes(term) ||\n reply.text.toLowerCase().includes(term)\n );\n};\n\nconst MODE_OPTIONS: ReadonlyArray<{\n mode: SupportChatReplyMode;\n label: string;\n}> = [\n { mode: SUPPORT_CHAT_REPLY_MODE.USER, label: 'Resposta ao usuário' },\n { mode: SUPPORT_CHAT_REPLY_MODE.INTERNAL_NOTE, label: 'Nota interna' },\n];\n\n/**\n * Box where a support agent answers a ticket: chooses between replying to\n * the user and writing an internal note (the field turns the note color),\n * types the text and sends it. Enter sends and Shift+Enter breaks the line.\n * With `quickReplies`, \"Respostas rápidas\" and \"/\" open the saved replies;\n * with `onOpenFaq`, a \"FAQ\" button shows up.\n *\n * @example\n * ```tsx\n * <SupportChatComposer\n * onSend={({ text, mode }) => sendReply(text, mode)}\n * onAttachImages={(files) => upload(files)}\n * />\n * ```\n */\nconst SupportChatComposer = ({\n onSend,\n defaultMode = SUPPORT_CHAT_REPLY_MODE.USER,\n onModeChange,\n onAttachImages,\n quickReplies,\n onOpenFaq,\n disabled = false,\n placeholder,\n className,\n}: SupportChatComposerProps) => {\n const [mode, setMode] = useState<SupportChatReplyMode>(defaultMode);\n const [text, setText] = useState('');\n const [quickMenuOpen, setQuickMenuOpen] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const hasQuickReplies = Boolean(quickReplies?.length);\n const isNote = mode === SUPPORT_CHAT_REPLY_MODE.INTERNAL_NOTE;\n // \"/\" at the start of the field opens the menu filtered by what follows it\n const slashQuery =\n hasQuickReplies && text.startsWith('/') ? text.slice(1) : null;\n const menuOpen = !disabled && (quickMenuOpen || slashQuery !== null);\n const visibleReplies = menuOpen\n ? filterQuickReplies(quickReplies ?? [], slashQuery ?? '')\n : [];\n const fieldPlaceholder =\n placeholder ??\n (hasQuickReplies\n ? 'Escreva sua resposta… digite / para respostas rápidas'\n : 'Escreva sua resposta…');\n\n const pickQuickReply = (reply: SupportQuickReply) => {\n setText(reply.text);\n setQuickMenuOpen(false);\n };\n\n const canSend = !disabled && text.trim().length > 0;\n\n const selectMode = (next: SupportChatReplyMode) => {\n if (next === mode) return;\n setMode(next);\n onModeChange?.(next);\n };\n\n const send = () => {\n if (!canSend) return;\n onSend({ text: text.trim(), mode });\n setText('');\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Escape' && menuOpen) {\n event.preventDefault();\n setQuickMenuOpen(false);\n if (slashQuery !== null) setText('');\n return;\n }\n // Enter sends; Shift+Enter and IME composition keep the line break\n if (\n event.key === 'Enter' &&\n !event.shiftKey &&\n !event.nativeEvent.isComposing\n ) {\n event.preventDefault();\n send();\n }\n };\n\n const handleFiles = (event: ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(event.target.files ?? []);\n // Reset so picking the same file again still fires onChange\n event.target.value = '';\n if (files.length > 0) onAttachImages?.(files);\n };\n\n return (\n <div\n className={cn('flex w-full flex-col gap-3', className)}\n data-testid=\"support-chat-composer\"\n >\n <div className=\"flex flex-wrap gap-2\">\n {MODE_OPTIONS.map((option) => (\n <Chips\n key={option.mode}\n selected={mode === option.mode}\n aria-pressed={mode === option.mode}\n onClick={() => selectMode(option.mode)}\n >\n {option.label}\n </Chips>\n ))}\n </div>\n\n <div className=\"relative\">\n {menuOpen && (\n <div\n role=\"menu\"\n aria-label=\"Respostas rápidas\"\n data-testid=\"support-chat-composer-quick-replies\"\n className=\"absolute bottom-full left-0 z-10 mb-2 flex max-h-60 w-full max-w-md flex-col overflow-y-auto rounded-lg border border-border-100 bg-background p-1 shadow-lg\"\n >\n {visibleReplies.length === 0 ? (\n <Text size=\"sm\" color=\"text-text-500\" className=\"px-3 py-2\">\n Nenhuma resposta rápida encontrada\n </Text>\n ) : (\n visibleReplies.map((reply) => (\n <Button\n key={reply.id}\n variant=\"raw\"\n role=\"menuitem\"\n onClick={() => pickQuickReply(reply)}\n className=\"flex flex-col items-start gap-0.5 rounded-md px-3 py-2 text-left hover:bg-background-50\"\n >\n <Text size=\"sm\" weight=\"semibold\" color=\"text-text-900\">\n {reply.title}\n </Text>\n <Text\n size=\"xs\"\n color=\"text-text-600\"\n className=\"line-clamp-1\"\n >\n {reply.text}\n </Text>\n </Button>\n ))\n )}\n </div>\n )}\n <TextArea\n aria-label=\"Resposta\"\n placeholder={\n isNote ? 'Escreva uma nota para a equipe…' : fieldPlaceholder\n }\n value={text}\n onChange={(event) => setText(event.target.value)}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n autoResize\n // One line to start (24px of text + 24px of padding); it grows as\n // the agent types, instead of the TextArea default of 96px\n minHeight={COMPOSER_MIN_HEIGHT}\n className={cn(\n 'rounded-lg',\n // Internal notes get the note color so they are not sent by mistake\n isNote && 'bg-exam-3 border-warning-200'\n )}\n data-mode={mode}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex flex-wrap items-center gap-3\">\n {onAttachImages && (\n <>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n hidden\n data-testid=\"support-chat-composer-file-input\"\n onChange={handleFiles}\n />\n <IconButton\n icon={<ImageIcon size={20} />}\n size=\"sm\"\n aria-label=\"Anexar imagem\"\n disabled={disabled}\n onClick={() => fileInputRef.current?.click()}\n />\n </>\n )}\n {hasQuickReplies && (\n <Button\n variant=\"link\"\n action=\"secondary\"\n size=\"small\"\n iconLeft={<ChatTeardropTextIcon size={18} />}\n aria-expanded={menuOpen}\n disabled={disabled}\n onClick={() => setQuickMenuOpen((open) => !open)}\n >\n Respostas rápidas\n </Button>\n )}\n {onOpenFaq && (\n <Button\n variant=\"link\"\n action=\"secondary\"\n size=\"small\"\n iconLeft={<LinkIcon size={18} />}\n disabled={disabled}\n onClick={onOpenFaq}\n >\n FAQ\n </Button>\n )}\n </div>\n\n <div className=\"flex items-center gap-4\">\n <Text size=\"xs\" color=\"text-text-500\">\n Enter envia · Shift+Enter quebra linha\n </Text>\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={send}\n disabled={!canSend}\n >\n Enviar\n </Button>\n </div>\n </div>\n </div>\n );\n};\n\nexport default SupportChatComposer;\n"]}
1
+ {"version":3,"sources":["/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-HSV5VZ4Z.js","../src/components/SupportChat/SupportChatComposer.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/analytica-frontend-lib/analytica-frontend-lib/dist/chunk-HSV5VZ4Z.js","sourcesContent":[null,"import { useRef, useState, type ChangeEvent, type KeyboardEvent } from 'react';\nimport { ImageIcon } from '@phosphor-icons/react/dist/csr/Image';\nimport { ChatTeardropTextIcon } from '@phosphor-icons/react/dist/csr/ChatTeardropText';\nimport { LinkIcon } from '@phosphor-icons/react/dist/csr/Link';\nimport Chips from '../Chips/Chips';\nimport TextArea from '../TextArea/TextArea';\nimport Button from '../Button/Button';\nimport IconButton from '../IconButton/IconButton';\nimport Text from '../Text/Text';\nimport {\n SUPPORT_CHAT_REPLY_MODE,\n type SupportChatReplyMode,\n} from '../../types/supportChat';\nimport type { SupportQuickReply } from '../../types/supportConversation';\nimport { cn } from '../../utils/utils';\n\n/**\n * Props of the SupportChatComposer component\n */\nexport interface SupportChatComposerProps {\n /** Called with the text and the chosen mode; the field is cleared afterwards */\n onSend: (data: { text: string; mode: SupportChatReplyMode }) => void;\n /** Mode selected when the composer mounts (default: reply to the user) */\n defaultMode?: SupportChatReplyMode;\n /** Called when the agent switches between reply and internal note */\n onModeChange?: (mode: SupportChatReplyMode) => void;\n /** Images picked with the image button; without it the button is hidden */\n onAttachImages?: (files: File[]) => void;\n /** Blocks sending (e.g. while a reply is being sent) */\n disabled?: boolean;\n /**\n * Saved replies. With them the \"Respostas rápidas\" button shows up and\n * typing \"/\" at the start of the field opens the same menu, filtered by\n * what follows the slash. Picking one replaces the text.\n */\n quickReplies?: SupportQuickReply[];\n /** Opens the FAQ search; without it the \"FAQ\" button is hidden */\n onOpenFaq?: () => void;\n /** Placeholder of the text field (mentions \"/\" when there are quick replies) */\n placeholder?: string;\n /** Extra classes for the root (it takes the full width of its parent) */\n className?: string;\n}\n\n/** Mode chips, in display order */\n/** Starting height of the text field, in px: a single line */\nconst COMPOSER_MIN_HEIGHT = 48;\n\n/** Quick replies whose title or text contains the query (case-insensitive) */\nconst filterQuickReplies = (\n replies: SupportQuickReply[],\n query: string\n): SupportQuickReply[] => {\n const term = query.trim().toLowerCase();\n if (!term) return replies;\n return replies.filter(\n (reply) =>\n reply.title.toLowerCase().includes(term) ||\n reply.text.toLowerCase().includes(term)\n );\n};\n\nconst MODE_OPTIONS: ReadonlyArray<{\n mode: SupportChatReplyMode;\n label: string;\n}> = [\n { mode: SUPPORT_CHAT_REPLY_MODE.USER, label: 'Resposta ao usuário' },\n { mode: SUPPORT_CHAT_REPLY_MODE.INTERNAL_NOTE, label: 'Nota interna' },\n];\n\n/**\n * Box where a support agent answers a ticket: chooses between replying to\n * the user and writing an internal note (the field turns the note color),\n * types the text and sends it. Enter sends and Shift+Enter breaks the line.\n * With `quickReplies`, \"Respostas rápidas\" and \"/\" open the saved replies;\n * with `onOpenFaq`, a \"FAQ\" button shows up.\n *\n * @example\n * ```tsx\n * <SupportChatComposer\n * onSend={({ text, mode }) => sendReply(text, mode)}\n * onAttachImages={(files) => upload(files)}\n * />\n * ```\n */\nconst SupportChatComposer = ({\n onSend,\n defaultMode = SUPPORT_CHAT_REPLY_MODE.USER,\n onModeChange,\n onAttachImages,\n quickReplies,\n onOpenFaq,\n disabled = false,\n placeholder,\n className,\n}: SupportChatComposerProps) => {\n const [mode, setMode] = useState<SupportChatReplyMode>(defaultMode);\n const [text, setText] = useState('');\n const [quickMenuOpen, setQuickMenuOpen] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const hasQuickReplies = Boolean(quickReplies?.length);\n const isNote = mode === SUPPORT_CHAT_REPLY_MODE.INTERNAL_NOTE;\n // \"/\" at the start of the field opens the menu filtered by what follows it\n const slashQuery =\n hasQuickReplies && text.startsWith('/') ? text.slice(1) : null;\n const menuOpen = !disabled && (quickMenuOpen || slashQuery !== null);\n const visibleReplies = menuOpen\n ? filterQuickReplies(quickReplies ?? [], slashQuery ?? '')\n : [];\n const fieldPlaceholder =\n placeholder ??\n (hasQuickReplies\n ? 'Escreva sua resposta… digite / para respostas rápidas'\n : 'Escreva sua resposta…');\n\n const pickQuickReply = (reply: SupportQuickReply) => {\n setText(reply.text);\n setQuickMenuOpen(false);\n };\n\n const canSend = !disabled && text.trim().length > 0;\n\n const selectMode = (next: SupportChatReplyMode) => {\n if (next === mode) return;\n setMode(next);\n onModeChange?.(next);\n };\n\n const send = () => {\n if (!canSend) return;\n onSend({ text: text.trim(), mode });\n setText('');\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === 'Escape' && menuOpen) {\n event.preventDefault();\n setQuickMenuOpen(false);\n if (slashQuery !== null) setText('');\n return;\n }\n // Enter sends; Shift+Enter and IME composition keep the line break\n if (\n event.key === 'Enter' &&\n !event.shiftKey &&\n !event.nativeEvent.isComposing\n ) {\n event.preventDefault();\n send();\n }\n };\n\n const handleFiles = (event: ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(event.target.files ?? []);\n // Reset so picking the same file again still fires onChange\n event.target.value = '';\n if (files.length > 0) onAttachImages?.(files);\n };\n\n return (\n <div\n className={cn('flex w-full flex-col gap-3', className)}\n data-testid=\"support-chat-composer\"\n >\n <div className=\"flex flex-wrap gap-2\">\n {MODE_OPTIONS.map((option) => (\n <Chips\n key={option.mode}\n selected={mode === option.mode}\n aria-pressed={mode === option.mode}\n onClick={() => selectMode(option.mode)}\n >\n {option.label}\n </Chips>\n ))}\n </div>\n\n <div className=\"relative\">\n {menuOpen && (\n <div\n role=\"menu\"\n aria-label=\"Respostas rápidas\"\n data-testid=\"support-chat-composer-quick-replies\"\n className=\"absolute bottom-full left-0 z-10 mb-2 flex max-h-60 w-full max-w-md flex-col overflow-y-auto rounded-lg border border-border-100 bg-background p-1 shadow-lg\"\n >\n {visibleReplies.length === 0 ? (\n <Text size=\"sm\" color=\"text-text-500\" className=\"px-3 py-2\">\n Nenhuma resposta rápida encontrada\n </Text>\n ) : (\n visibleReplies.map((reply) => (\n <Button\n key={reply.id}\n variant=\"raw\"\n role=\"menuitem\"\n onClick={() => pickQuickReply(reply)}\n className=\"flex flex-col items-start gap-0.5 rounded-md px-3 py-2 text-left hover:bg-background-50\"\n >\n <Text size=\"sm\" weight=\"semibold\" color=\"text-text-900\">\n {reply.title}\n </Text>\n <Text\n size=\"xs\"\n color=\"text-text-600\"\n className=\"line-clamp-1\"\n >\n {reply.text}\n </Text>\n </Button>\n ))\n )}\n </div>\n )}\n <TextArea\n aria-label=\"Resposta\"\n placeholder={\n isNote ? 'Escreva uma nota para a equipe…' : fieldPlaceholder\n }\n value={text}\n onChange={(event) => setText(event.target.value)}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n autoResize\n // One line to start (24px of text + 24px of padding); it grows as\n // the agent types, instead of the TextArea default of 96px\n minHeight={COMPOSER_MIN_HEIGHT}\n className={cn(\n 'rounded-lg',\n // Internal notes get the note color so they are not sent by mistake\n isNote && 'bg-exam-3 border-warning-200'\n )}\n data-mode={mode}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex flex-wrap items-center gap-3\">\n {onAttachImages && (\n <>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n multiple\n hidden\n data-testid=\"support-chat-composer-file-input\"\n onChange={handleFiles}\n />\n <IconButton\n icon={<ImageIcon size={20} />}\n size=\"sm\"\n aria-label=\"Anexar imagem\"\n disabled={disabled}\n onClick={() => fileInputRef.current?.click()}\n />\n </>\n )}\n {hasQuickReplies && (\n <Button\n variant=\"link\"\n action=\"secondary\"\n size=\"small\"\n iconLeft={<ChatTeardropTextIcon size={18} />}\n aria-expanded={menuOpen}\n disabled={disabled}\n onClick={() => setQuickMenuOpen((open) => !open)}\n >\n Respostas rápidas\n </Button>\n )}\n {onOpenFaq && (\n <Button\n variant=\"link\"\n action=\"secondary\"\n size=\"small\"\n iconLeft={<LinkIcon size={18} />}\n disabled={disabled}\n onClick={onOpenFaq}\n >\n FAQ\n </Button>\n )}\n </div>\n\n <div className=\"flex items-center gap-4\">\n <Text size=\"xs\" color=\"text-text-500\">\n Enter envia · Shift+Enter quebra linha\n </Text>\n <Button\n variant=\"solid\"\n action=\"primary\"\n size=\"medium\"\n onClick={send}\n disabled={!canSend}\n >\n Enviar\n </Button>\n </div>\n </div>\n </div>\n );\n};\n\nexport default SupportChatComposer;\n"]}
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  SUPPORT_CHAT_MESSAGE_TYPE,
6
6
  SUPPORT_CHAT_SEND_STATUS
7
- } from "./chunk-7ZB3YHF2.mjs";
7
+ } from "./chunk-C6LC5LD5.mjs";
8
8
  import {
9
9
  Button_default
10
10
  } from "./chunk-ZQZBOTZJ.mjs";
@@ -270,4 +270,4 @@ function SupportChatMessage({
270
270
  export {
271
271
  SupportChatMessage
272
272
  };
273
- //# sourceMappingURL=chunk-KWXOYHYK.mjs.map
273
+ //# sourceMappingURL=chunk-P7MORXSE.mjs.map