@better-zap/react 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Better Zap
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @better-zap/react
2
+
3
+ React UI components for Better Zap conversations and message views.
package/dist/index.cjs ADDED
@@ -0,0 +1,614 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let react = require("react");
25
+ react = __toESM(react);
26
+ let clsx = require("clsx");
27
+ let tailwind_merge = require("tailwind-merge");
28
+ let react_jsx_runtime = require("react/jsx-runtime");
29
+ let _hugeicons_react = require("@hugeicons/react");
30
+ let _hugeicons_core_free_icons = require("@hugeicons/core-free-icons");
31
+ let class_variance_authority = require("class-variance-authority");
32
+ //#region src/react/utils.ts
33
+ function cn(...inputs) {
34
+ return (0, tailwind_merge.twMerge)((0, clsx.clsx)(inputs));
35
+ }
36
+ //#endregion
37
+ //#region src/react/whatsapp-dashboard.tsx
38
+ const MOBILE_BREAKPOINT = 1024;
39
+ function useIsMobile() {
40
+ const [isMobile, setIsMobile] = (0, react.useState)(false);
41
+ (0, react.useEffect)(() => {
42
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
43
+ const onChange = (e) => {
44
+ setIsMobile(e.matches);
45
+ };
46
+ onChange(mql);
47
+ mql.addEventListener("change", onChange);
48
+ return () => mql.removeEventListener("change", onChange);
49
+ }, []);
50
+ return isMobile;
51
+ }
52
+ const WhatsappDashboardContext = (0, react.createContext)(null);
53
+ function useWhatsappDashboard() {
54
+ const ctx = (0, react.useContext)(WhatsappDashboardContext);
55
+ if (!ctx) throw new Error("useWhatsappDashboard must be used within <WhatsappDashboard>");
56
+ return ctx;
57
+ }
58
+ function WhatsappDashboard({ children, className, defaultMobileView = "list", ...props }) {
59
+ const isMobile = useIsMobile();
60
+ const [mobileView, setMobileView] = (0, react.useState)(defaultMobileView);
61
+ const handleSetMobileView = (0, react.useCallback)((view) => {
62
+ setMobileView(view);
63
+ }, []);
64
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WhatsappDashboardContext.Provider, {
65
+ value: {
66
+ isMobile,
67
+ mobileView,
68
+ setMobileView: handleSetMobileView
69
+ },
70
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
71
+ className: cn("bg-background flex h-full w-full overflow-hidden", className),
72
+ ...props,
73
+ children
74
+ })
75
+ });
76
+ }
77
+ //#endregion
78
+ //#region src/react/message-bubble.tsx
79
+ const bubbleVariants = (0, class_variance_authority.cva)("relative shadow-[0_1px_0.5px_rgba(11,20,26,0.13)] rounded-lg px-3 py-2 max-w-[65%]", { variants: { variant: {
80
+ outgoing: "bg-green-100 text-green-900 rounded-tr-none",
81
+ incoming: "bg-gray-100 text-gray-900 rounded-tl-none",
82
+ failed: "bg-red-100 text-red-900 rounded-tr-none border border-red-200"
83
+ } } });
84
+ const statusVariants = (0, class_variance_authority.cva)("text-[13px] leading-none", {
85
+ variants: { variant: {
86
+ default: "opacity-60",
87
+ read: "text-blue-500",
88
+ failed: "text-red-500"
89
+ } },
90
+ defaultVariants: { variant: "default" }
91
+ });
92
+ function MessageBubble({ content, sender, timestamp, status, templateName, label, className, ...props }) {
93
+ const isIncoming = sender === "user";
94
+ const isFailed = status === "failed";
95
+ const bubbleVariant = isIncoming ? "incoming" : isFailed ? "failed" : "outgoing";
96
+ const statusVariant = status === "read" ? "read" : isFailed ? "failed" : "default";
97
+ const displayContent = content || (templateName ? `[Template: ${templateName}]` : "[Conteúdo não disponível]");
98
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
99
+ className: cn("flex w-full mb-1", isIncoming ? "justify-start" : "justify-end", className),
100
+ ...props,
101
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
102
+ className: bubbleVariants({ variant: bubbleVariant }),
103
+ children: [
104
+ label && !isIncoming && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
105
+ className: "mb-1 block text-xs font-medium opacity-70",
106
+ children: label
107
+ }),
108
+ templateName && !label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
109
+ className: "mb-1 border-b border-black/5 pb-1",
110
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
111
+ className: "text-[11px] font-medium opacity-70",
112
+ children: ["📋 ", templateName]
113
+ })
114
+ }),
115
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
116
+ className: "text-[14.5px] leading-normal whitespace-pre-wrap break-words select-text",
117
+ children: [
118
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormattedMessage, { text: displayContent }),
119
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
120
+ className: "float-right -mb-1 ml-2 mt-1.5 flex items-center justify-end gap-1 shrink-0 h-[15px] select-none",
121
+ children: [timestamp && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
122
+ className: "text-[11px] opacity-60 leading-none whitespace-nowrap",
123
+ children: timestamp
124
+ }), !isIncoming && status && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
125
+ className: statusVariants({ variant: statusVariant }),
126
+ children: status === "read" || status === "delivered" ? "✓✓" : isFailed ? "✕" : "✓"
127
+ })]
128
+ }),
129
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "clear-both" })
130
+ ]
131
+ })
132
+ ]
133
+ })
134
+ });
135
+ }
136
+ function FormattedMessage({ text }) {
137
+ if (!text) return null;
138
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: text.split(/(\n)/g).map((line, lineIndex) => {
139
+ if (line === "\n") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}, lineIndex);
140
+ if (!line) return null;
141
+ const parts = line.split(/(```[\s\S]*?```|`[^`]+`|\*[^\s*](?:[^*]*[^\s*])?\*|_[^\s_](?:[^_]*[^\s_])?_|~[^\s~](?:[^~]*[^\s~])?~|https?:\/\/[^\s]+)/g);
142
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: parts.map((part, index) => {
143
+ if (!part) return null;
144
+ if (part.startsWith("```") && part.endsWith("```")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
145
+ className: "my-1 block whitespace-pre-wrap rounded bg-black/5 px-1 py-0.5 font-mono text-[13px]",
146
+ children: part.slice(3, -3)
147
+ }, index);
148
+ if (part.startsWith("`") && part.endsWith("`")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
149
+ className: "rounded bg-black/5 px-1 font-mono text-[13px] text-[#df0165]",
150
+ children: part.slice(1, -1)
151
+ }, index);
152
+ if (part.startsWith("*") && part.endsWith("*")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
153
+ className: "font-bold",
154
+ children: part.slice(1, -1)
155
+ }, index);
156
+ if (part.startsWith("_") && part.endsWith("_")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("em", {
157
+ className: "italic",
158
+ children: part.slice(1, -1)
159
+ }, index);
160
+ if (part.startsWith("~") && part.endsWith("~")) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("del", {
161
+ className: "text-gray-500 line-through",
162
+ children: part.slice(1, -1)
163
+ }, index);
164
+ if (part.match(/^https?:\/\/[^\s]+$/)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
165
+ href: part,
166
+ target: "_blank",
167
+ rel: "noopener noreferrer",
168
+ className: "text-[#027eb5] hover:underline",
169
+ children: part
170
+ }, index);
171
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: part }, index);
172
+ }) }, lineIndex);
173
+ }) });
174
+ }
175
+ //#endregion
176
+ //#region src/date.ts
177
+ function getDisplayDate(dateStr) {
178
+ const dateObj = new Date(dateStr);
179
+ const now = /* @__PURE__ */ new Date();
180
+ const isToday = dateObj.toDateString() === now.toDateString();
181
+ const yesterday = new Date(now);
182
+ yesterday.setDate(yesterday.getDate() - 1);
183
+ const isYesterday = dateObj.toDateString() === yesterday.toDateString();
184
+ if (isToday) return "HOJE";
185
+ else if (isYesterday) return "ONTEM";
186
+ else return dateObj.toLocaleDateString("pt-BR", {
187
+ day: "2-digit",
188
+ month: "2-digit",
189
+ year: "numeric"
190
+ });
191
+ }
192
+ //#endregion
193
+ //#region src/react/message-view.tsx
194
+ function MessageView({ children, className, ...props }) {
195
+ const { isMobile, mobileView } = useWhatsappDashboard();
196
+ const hasContent = react.default.Children.count(children) > 0;
197
+ const isVisible = !isMobile || mobileView === "chat";
198
+ if (!hasContent) {
199
+ if (isMobile) return null;
200
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageViewEmpty, {
201
+ className,
202
+ ...props
203
+ });
204
+ }
205
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
206
+ className: cn("relative flex flex-1 flex-col bg-[#efeae2]", className),
207
+ style: isVisible ? void 0 : { display: "none" },
208
+ ...props,
209
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
210
+ position: "absolute",
211
+ backgroundImage: `url(${new URL("./wpp-bg.webp", require("url").pathToFileURL(__filename).href).href})`,
212
+ backgroundRepeat: "repeat",
213
+ opacity: .15,
214
+ inset: 0
215
+ } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
216
+ className: "relative flex flex-1 flex-col min-h-0",
217
+ children
218
+ })]
219
+ });
220
+ }
221
+ function MessageViewHeader({ conversation, onBack, onInfoClick, className }) {
222
+ const { isMobile, setMobileView } = useWhatsappDashboard();
223
+ const handleBack = () => {
224
+ setMobileView("list");
225
+ onBack?.();
226
+ };
227
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
228
+ className: cn("flex h-16 shrink-0 items-center justify-between border-b bg-[#f0f2f5] px-4 z-20", className),
229
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
230
+ className: "flex items-center gap-3",
231
+ children: [isMobile && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
232
+ type: "button",
233
+ className: "inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-black/5",
234
+ onClick: handleBack,
235
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
236
+ icon: _hugeicons_core_free_icons.ArrowLeft02Icon,
237
+ size: 20
238
+ })
239
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
240
+ className: "flex flex-col",
241
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
242
+ className: "text-[15px] font-medium text-[#111b21] leading-tight",
243
+ children: conversation.contactName || conversation.phone
244
+ }), conversation.contactName && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
245
+ className: "text-sm text-[#667781] leading-tight",
246
+ children: conversation.phone
247
+ })]
248
+ })]
249
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
250
+ type: "button",
251
+ className: "inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-black/5",
252
+ onClick: onInfoClick,
253
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
254
+ icon: _hugeicons_core_free_icons.InformationCircleIcon,
255
+ size: 20
256
+ })
257
+ })]
258
+ });
259
+ }
260
+ const SCROLL_TOP_THRESHOLD = 50;
261
+ function MessageViewContent({ children, autoScroll = true, onScrollTop, className, ...props }) {
262
+ const scrollRef = (0, react.useRef)(null);
263
+ const prevScrollHeightRef = (0, react.useRef)(0);
264
+ (0, react.useEffect)(() => {
265
+ if (autoScroll && scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
266
+ }, [children, autoScroll]);
267
+ (0, react.useEffect)(() => {
268
+ const el = scrollRef.current;
269
+ if (!el) return;
270
+ const newScrollHeight = el.scrollHeight;
271
+ const prevScrollHeight = prevScrollHeightRef.current;
272
+ if (prevScrollHeight > 0 && newScrollHeight > prevScrollHeight) {
273
+ const addedHeight = newScrollHeight - prevScrollHeight;
274
+ if (el.scrollTop < SCROLL_TOP_THRESHOLD + addedHeight) el.scrollTop = addedHeight;
275
+ }
276
+ prevScrollHeightRef.current = newScrollHeight;
277
+ });
278
+ const handleScroll = (0, react.useCallback)(() => {
279
+ const el = scrollRef.current;
280
+ if (!el || !onScrollTop) return;
281
+ if (el.scrollTop <= SCROLL_TOP_THRESHOLD) onScrollTop();
282
+ }, [onScrollTop]);
283
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
284
+ ref: scrollRef,
285
+ className: cn("flex flex-1 flex-col overflow-y-auto p-4 pb-0 chat-scrollbar", className),
286
+ onScroll: handleScroll,
287
+ ...props,
288
+ children
289
+ });
290
+ }
291
+ function MessageList({ messages, renderMessageLabel, className }) {
292
+ const messageGroups = (0, react.useMemo)(() => {
293
+ const groups = [];
294
+ let currentGroup = null;
295
+ messages.forEach((msg) => {
296
+ const displayDate = getDisplayDate(msg.sentAt);
297
+ if (!currentGroup || currentGroup.date !== displayDate) {
298
+ currentGroup = {
299
+ date: displayDate,
300
+ messages: []
301
+ };
302
+ groups.push(currentGroup);
303
+ }
304
+ currentGroup.messages.push(msg);
305
+ });
306
+ return groups;
307
+ }, [messages]);
308
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
309
+ className: cn("flex flex-col pb-4", className),
310
+ children: messageGroups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
311
+ className: "flex flex-col gap-1",
312
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DateDivider, { date: group.date }), group.messages.map((msg) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageBubble, {
313
+ content: msg.content || "",
314
+ sender: msg.direction === "incoming" ? "user" : "bot",
315
+ timestamp: new Date(msg.sentAt).toLocaleTimeString("pt-BR", {
316
+ hour: "2-digit",
317
+ minute: "2-digit"
318
+ }),
319
+ status: msg.status,
320
+ templateName: msg.templateName || void 0,
321
+ label: renderMessageLabel?.(msg)
322
+ }, msg.id))]
323
+ }, group.date))
324
+ });
325
+ }
326
+ function DateDivider({ date }) {
327
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
328
+ className: "sticky top-0 z-10 flex justify-center w-full py-2 pointer-events-none",
329
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
330
+ className: "bg-white border border-[#e9edef] shadow-[0_1px_0.5px_rgba(11,20,26,0.13)] text-[#54656f] text-[12.5px] font-medium px-3 py-1.5 rounded-lg uppercase pointer-events-auto",
331
+ children: date
332
+ })
333
+ });
334
+ }
335
+ function MessageViewEmpty({ className, ...props }) {
336
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
337
+ className: cn("relative flex flex-1 flex-col items-center justify-center bg-[#f8f9fa] text-[#667781] p-6", className),
338
+ ...props,
339
+ children: [
340
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
341
+ className: "mb-8 flex h-48 w-48 items-center justify-center rounded-full bg-[#f0f2f5] shadow-sm",
342
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
343
+ icon: _hugeicons_core_free_icons.Message01Icon,
344
+ size: 80,
345
+ className: "text-[#bbc5cb]"
346
+ })
347
+ }),
348
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
349
+ className: "mb-3 text-2xl font-semibold text-[#41525d]",
350
+ children: "Better Zap"
351
+ }),
352
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
353
+ className: "max-w-sm space-y-3 text-center",
354
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
355
+ className: "text-[15px] leading-relaxed",
356
+ children: [
357
+ "Esta é uma interface dedicada para visualização e monitoramento de mensagens da ",
358
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "API Oficial do WhatsApp" }),
359
+ "."
360
+ ]
361
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
362
+ className: "text-sm opacity-80",
363
+ children: "Acompanhe o histórico de conversas, verifique o status de entrega e gerencie as interações do Cloud API de forma profissional."
364
+ })]
365
+ })
366
+ ]
367
+ });
368
+ }
369
+ //#endregion
370
+ //#region src/react/conversation-search.tsx
371
+ function ConversationSearch({ value, onChange, className }) {
372
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
373
+ className: cn("border-b border-[#e9edef] shrink-0 p-2", className),
374
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
375
+ className: "flex items-center bg-[#f0f2f5] rounded-full px-4 h-[38px] gap-3 focus-within:bg-white focus-within:ring-1 focus-within:ring-green-200 focus-within:shadow-md transition-all border border-transparent focus-within:border-transparent",
376
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
377
+ icon: _hugeicons_core_free_icons.Search01Icon,
378
+ size: 18,
379
+ className: "text-[#54656f] shrink-0"
380
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
381
+ type: "text",
382
+ placeholder: "Buscar conversa",
383
+ value,
384
+ onChange: (e) => onChange(e.target.value),
385
+ className: "flex-1 border-none bg-transparent text-[15px] text-[#111b21] focus:outline-none h-full placeholder:text-[#667781]"
386
+ })]
387
+ })
388
+ });
389
+ }
390
+ //#endregion
391
+ //#region src/react/conversation-list.tsx
392
+ function ConversationList({ conversations, isLoading, isError, selectedConversationId, onSelect, className }) {
393
+ const { isMobile, mobileView, setMobileView } = useWhatsappDashboard();
394
+ const [search, setSearch] = (0, react.useState)("");
395
+ const filtered = conversations.filter((c) => c.phone.includes(search) || c.contactName?.toLowerCase().includes(search.toLowerCase()));
396
+ const handleSelect = (id) => {
397
+ onSelect(id);
398
+ setMobileView("chat");
399
+ };
400
+ const isVisible = !isMobile || mobileView === "list";
401
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
402
+ className: cn("flex flex-col h-full bg-white border-r border-[#e9edef]", isMobile ? "w-full" : "min-w-[320px] max-w-105", className),
403
+ style: isVisible ? void 0 : { display: "none" },
404
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationSearch, {
405
+ value: search,
406
+ onChange: setSearch
407
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
408
+ className: "flex-1 overflow-y-auto overflow-x-hidden chat-scrollbar",
409
+ children: isLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
410
+ className: "flex items-center justify-center h-full text-sm text-[#667781]",
411
+ children: "Carregando..."
412
+ }) : isError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
413
+ className: "flex items-center justify-center h-full text-sm text-red-500",
414
+ children: "Erro ao carregar conversas"
415
+ }) : filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
416
+ className: "flex flex-col items-center justify-center h-full gap-2 text-[#667781]",
417
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
418
+ icon: _hugeicons_core_free_icons.Message01Icon,
419
+ size: 32
420
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
421
+ className: "text-sm",
422
+ children: "Nenhuma conversa encontrada"
423
+ })]
424
+ }) : filtered.map((conversation) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationItem, {
425
+ conversation,
426
+ isSelected: selectedConversationId === conversation.id,
427
+ onClick: () => handleSelect(conversation.id)
428
+ }, conversation.id))
429
+ })]
430
+ });
431
+ }
432
+ function ConversationItem({ conversation, isSelected, onClick }) {
433
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
434
+ onClick,
435
+ "data-selected": isSelected,
436
+ className: "group flex items-center w-full h-[72px] px-3 gap-3 transition-all cursor-pointer text-left relative overflow-hidden hover:bg-[#f5f6f6] data-[selected=true]:bg-[#075e54] data-[selected=true]:hover:bg-[#064940]",
437
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
438
+ className: "w-[49px] h-[49px] rounded-full bg-[#dfe5e7] flex items-center justify-center shrink-0 group-data-[selected=true]:bg-white/20",
439
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
440
+ icon: _hugeicons_core_free_icons.UserIcon,
441
+ size: 28,
442
+ className: "text-[#aebac1] group-data-[selected=true]:text-white/80"
443
+ })
444
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
445
+ className: "flex-1 min-w-0 border-b border-[#f2f2f2] h-full flex flex-col justify-center pr-1 group-last:border-none group-data-[selected=true]:border-transparent",
446
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
447
+ className: "flex justify-between items-baseline mb-0.5",
448
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
449
+ className: "text-[17px] font-normal text-[#111b21] truncate group-data-[selected=true]:text-white",
450
+ children: conversation.contactName || formatPhone(conversation.phone)
451
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
452
+ className: "text-xs text-[#667781] shrink-0 group-data-[selected=true]:text-white/90",
453
+ children: formatTime(conversation.lastMessageAt)
454
+ })]
455
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
456
+ className: "flex justify-between items-center gap-2",
457
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
458
+ className: "text-[14px] text-[#667781] truncate group-data-[selected=true]:text-white/90",
459
+ children: [conversation.lastDirection === "incoming" ? "" : "Você: ", conversation.lastMessagePreview || "Sem mensagem"]
460
+ }), conversation.unreadCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
461
+ className: "bg-[#25d366] text-white text-[11px] font-bold rounded-full min-w-[20px] h-[20px] flex items-center justify-center px-1.5 shrink-0 group-data-[selected=true]:bg-white",
462
+ children: conversation.unreadCount
463
+ })]
464
+ })]
465
+ })]
466
+ });
467
+ }
468
+ function formatPhone(phone) {
469
+ if (phone.length === 13 && phone.startsWith("55")) return `(${phone.slice(2, 4)}) ${phone.slice(4, 9)}-${phone.slice(9)}`;
470
+ return phone;
471
+ }
472
+ function formatTime(dateStr) {
473
+ try {
474
+ const date = new Date(dateStr);
475
+ const now = /* @__PURE__ */ new Date();
476
+ if (date.toDateString() === now.toDateString()) return date.toLocaleTimeString("pt-BR", {
477
+ hour: "2-digit",
478
+ minute: "2-digit"
479
+ });
480
+ const yesterday = new Date(now);
481
+ yesterday.setDate(yesterday.getDate() - 1);
482
+ if (date.toDateString() === yesterday.toDateString()) return "Ontem";
483
+ return date.toLocaleDateString("pt-BR", {
484
+ day: "2-digit",
485
+ month: "2-digit"
486
+ });
487
+ } catch {
488
+ return "";
489
+ }
490
+ }
491
+ //#endregion
492
+ //#region src/react/message-input.tsx
493
+ function MessageInput({ onSend, disabled, placeholder = "Digite uma mensagem", className, contextWindowOpen = true }) {
494
+ const [text, setText] = (0, react.useState)("");
495
+ const [isSending, setIsSending] = (0, react.useState)(false);
496
+ const textareaRef = (0, react.useRef)(null);
497
+ const isDisabled = disabled || isSending || !contextWindowOpen;
498
+ const handleSend = async () => {
499
+ const trimmedText = text.trim();
500
+ if (!trimmedText || isDisabled) return;
501
+ setIsSending(true);
502
+ try {
503
+ await onSend(trimmedText);
504
+ setText("");
505
+ if (textareaRef.current) textareaRef.current.style.height = "auto";
506
+ } finally {
507
+ setIsSending(false);
508
+ }
509
+ };
510
+ const handleKeyDown = (e) => {
511
+ if (e.key === "Enter" && !e.shiftKey) {
512
+ e.preventDefault();
513
+ handleSend();
514
+ }
515
+ };
516
+ (0, react.useEffect)(() => {
517
+ if (textareaRef.current) {
518
+ textareaRef.current.style.height = "auto";
519
+ textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
520
+ }
521
+ }, [text]);
522
+ const hasText = text.trim().length > 0;
523
+ if (!contextWindowOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
524
+ className: cn("w-full flex flex-col p-4 z-10 shrink-0", className),
525
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
526
+ className: "flex items-center gap-2 px-4 py-3 min-h-[62px] bg-[#fef3c7] rounded-2xl border border-[#f59e0b]/20",
527
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
528
+ icon: _hugeicons_core_free_icons.Clock01Icon,
529
+ size: 20,
530
+ className: "text-[#92400e] shrink-0"
531
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
532
+ className: "text-[13px] leading-[18px] text-[#92400e]",
533
+ children: "A janela de 24h expirou. Mensagens de texto livre só podem ser enviadas dentro de 24h após a última mensagem do contato."
534
+ })]
535
+ })
536
+ });
537
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
538
+ className: cn("w-full flex flex-col p-4 z-10 shrink-0", className),
539
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
540
+ className: "flex items-center gap-1 px-2 py-1 min-h-[62px] bg-white rounded-2xl shadow-lg border border-gray-100",
541
+ children: [
542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
543
+ className: "flex items-center gap-1 text-[#54656f] shrink-0",
544
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
545
+ type: "button",
546
+ "aria-label": "Emojis",
547
+ className: "p-2 rounded-full hover:bg-black/5 transition-colors",
548
+ title: "Emojis",
549
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
550
+ icon: _hugeicons_core_free_icons.SmileIcon,
551
+ size: 24
552
+ })
553
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
554
+ type: "button",
555
+ "aria-label": "Anexar arquivo",
556
+ className: "p-2 rounded-full hover:bg-black/5 transition-colors",
557
+ title: "Anexar",
558
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
559
+ icon: _hugeicons_core_free_icons.Add01Icon,
560
+ size: 24
561
+ })
562
+ })]
563
+ }),
564
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
565
+ className: "flex-1 flex items-center min-h-[42px] py-3 px-2",
566
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
567
+ ref: textareaRef,
568
+ className: "flex-1 bg-transparent border-none text-[15px] leading-[22px] text-[#111b21] resize-none focus:outline-none max-h-[120px] placeholder:text-[#8696a0]",
569
+ name: "message",
570
+ "aria-label": "Mensagem",
571
+ autoComplete: "off",
572
+ placeholder: isSending ? "Enviando..." : placeholder,
573
+ value: text,
574
+ onChange: (e) => setText(e.target.value),
575
+ onKeyDown: handleKeyDown,
576
+ rows: 1,
577
+ disabled: isDisabled
578
+ })
579
+ }),
580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
581
+ className: "flex items-center pr-1 shrink-0",
582
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
583
+ type: "button",
584
+ "aria-label": hasText ? "Enviar" : "Gravar áudio",
585
+ className: cn("p-2 rounded-full transition-colors disabled:opacity-40 disabled:cursor-not-allowed", hasText ? "text-[#00a884]" : "text-[#54656f] hover:bg-black/5"),
586
+ onClick: hasText ? handleSend : void 0,
587
+ disabled: isDisabled,
588
+ children: hasText ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
589
+ icon: _hugeicons_core_free_icons.Sent02Icon,
590
+ size: 24
591
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
592
+ icon: _hugeicons_core_free_icons.Mic01Icon,
593
+ size: 24
594
+ })
595
+ })
596
+ })
597
+ ]
598
+ })
599
+ });
600
+ }
601
+ //#endregion
602
+ exports.ConversationItem = ConversationItem;
603
+ exports.ConversationList = ConversationList;
604
+ exports.FormattedMessage = FormattedMessage;
605
+ exports.MessageBubble = MessageBubble;
606
+ exports.MessageInput = MessageInput;
607
+ exports.MessageList = MessageList;
608
+ exports.MessageView = MessageView;
609
+ exports.MessageViewContent = MessageViewContent;
610
+ exports.MessageViewEmpty = MessageViewEmpty;
611
+ exports.MessageViewHeader = MessageViewHeader;
612
+ exports.WhatsappDashboard = WhatsappDashboard;
613
+ exports.cn = cn;
614
+ exports.useWhatsappDashboard = useWhatsappDashboard;