@triadxyz/widgets 0.0.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- 'use strict';var jsxRuntime=require('react/jsx-runtime'),z=require('react'),lucideReact=require('lucide-react'),clsx=require('clsx'),tailwindMerge=require('tailwind-merge'),T=require('socket.io-client'),dateFns=require('date-fns');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var z__default=/*#__PURE__*/_interopDefault(z);var T__default=/*#__PURE__*/_interopDefault(T);var W=({label:r,variant:h="primary",className:x="",...c})=>jsxRuntime.jsx("button",{className:`px-4 py-2 rounded-lg font-medium transition-colors ${{primary:"bg-blue-600 text-white hover:bg-blue-700",secondary:"bg-gray-200 text-gray-800 hover:bg-gray-300"}[h]} ${x}`,...c,children:r});function p(...r){return tailwindMerge.twMerge(clsx.clsx(r))}var D=T__default.default("https://beta.triadfi.co",{path:"/socket.io",reconnection:true,reconnectionAttempts:5,reconnectionDelay:1e3,randomizationFactor:.5,timeout:5e3,transports:["websocket"]}),a=D;function w({authority:r,customerAuthority:h}){let[x,c]=z.useState([]),[l,b]=z.useState(0),n=h?.trim();return z.useEffect(()=>{if(!n)return;let u=s=>{c(s);},m=s=>{c(f=>[...f,s]);},o=s=>{b(s.count);},g=()=>{let s=r?{authority:r,customerAuthority:n}:{customerAuthority:n};a.emit("chat:join",s,f=>{f?.error&&console.error("join error",f.error);});};return a.on("chat:history",u),a.on("chat:message",m),a.on("chat:users",o),a.on("connect",g),g(),()=>{a.off("chat:history",u),a.off("chat:message",m),a.off("chat:users",o),a.off("connect",g);}},[r,n]),{messages:x,onlineCount:l,sendMessage:u=>new Promise(m=>{if(!r||!n){m({error:"INVALID_REQUEST"});return}a.emit("chat:message",{authority:r,customerAuthority:n,message:u},o=>{o?.error&&console.error("send error",o.error),m(o??{error:"INTERNAL_ERROR"});});})}}var K=200;function ie({isOpen:r,onClose:h,title:x="Live Chat",theme:c,classNames:l,renderMessage:b,renderHeader:n,authority:d,customerAuthority:u,onConnectWallet:m}){let[o,g]=z.useState(""),s=z.useRef(null),f=c?.primaryColor||"#FF3D00",N=c?.background||"var(--chat-bg)",{sendMessage:R,onlineCount:M,messages:y}=w({authority:d||void 0,customerAuthority:u}),L=()=>{s.current?.scrollIntoView({behavior:"smooth"});};z.useEffect(()=>{L();},[y]);let v=()=>{let e=o.trim();if(e){if(!d){m?.();return}g(""),R(e);}},A=e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),v());};return r?jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx("style",{children:`
1
+ 'use strict';var jsxRuntime=require('react/jsx-runtime'),X=require('react'),lucideReact=require('lucide-react'),clsx=require('clsx'),tailwindMerge=require('tailwind-merge'),U=require('socket.io-client'),dateFns=require('date-fns');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var X__default=/*#__PURE__*/_interopDefault(X);var U__default=/*#__PURE__*/_interopDefault(U);var ne=({label:t,variant:u="primary",className:h="",...g})=>jsxRuntime.jsx("button",{className:`px-4 py-2 rounded-lg font-medium transition-colors ${{primary:"bg-blue-600 text-white hover:bg-blue-700",secondary:"bg-gray-200 text-gray-800 hover:bg-gray-300"}[u]} ${h}`,...g,children:t});function y(...t){return tailwindMerge.twMerge(clsx.clsx(t))}var V=U__default.default("https://beta.triadfi.co",{path:"/socket.io",reconnection:true,reconnectionAttempts:5,reconnectionDelay:1e3,randomizationFactor:.5,timeout:5e3,transports:["websocket"]}),i=V;function T({authority:t,customerAuthority:u}){let[h,g]=X.useState([]),[d,M]=X.useState(0),c=u?.trim();return X.useEffect(()=>{if(!c)return;let b=o=>{g(o);},p=o=>{g(f=>[...f,o]);},a=o=>{M(o.count);},v=()=>{let o=t?{authority:t,customerAuthority:c}:{customerAuthority:c};i.emit("chat:join",o,f=>{f?.error&&console.error("join error",f.error);});};return i.on("chat:history",b),i.on("chat:message",p),i.on("chat:users",a),i.on("connect",v),v(),()=>{i.off("chat:history",b),i.off("chat:message",p),i.off("chat:users",a),i.off("connect",v);}},[t,c]),{messages:h,onlineCount:d,sendMessage:b=>new Promise(p=>{if(!t||!c){p({error:"INVALID_REQUEST"});return}i.emit("chat:message",{authority:t,customerAuthority:c,message:b},a=>{a?.error&&console.error("send error",a.error),p(a??{error:"INTERNAL_ERROR"});});})}}var Z=200,ee=80;function H(t){return t?[t.timestamp,t.authority??"anonymous",t.name,t.message].join(":"):null}function te(t,u=ee){return t.scrollHeight-t.scrollTop-t.clientHeight<=u}function re(t){return `${t} ${t===1?"previsao":"previsoes"}`}function Ce({isOpen:t,onClose:u,title:h="Live Chat",theme:g,classNames:d,renderMessage:M,renderHeader:c,authority:m,customerAuthority:b,onConnectWallet:p}){let[a,v]=X.useState(""),o=X.useRef(null),f=X.useRef(false),k=X.useRef(true),C=X.useRef(null),E=g?.primaryColor||"#FF3D00",P=g?.background||"var(--chat-bg)",{sendMessage:D,onlineCount:B,messages:x}=T({authority:m||void 0,customerAuthority:b}),S=()=>{let r=o.current;return {firstMessageKey:H(x[0]),lastMessageKey:H(x[x.length-1]),messageCount:x.length,scrollHeight:r?.scrollHeight??0}},K=(r="auto")=>{let e=o.current;e&&(e.scrollTo({top:e.scrollHeight,behavior:r}),k.current=true);},_=r=>{let e=o.current;if(!e)return;let s=e.scrollHeight-r.scrollHeight;s!==0&&(e.scrollTop+=s);},$=()=>{let r=o.current;r&&(k.current=te(r));};X.useLayoutEffect(()=>{if(!t){f.current=false,k.current=true,C.current=null;return}if(!o.current)return;let e=S(),s=C.current;if(!f.current){if(x.length===0){C.current=e;return}K("auto"),f.current=true,C.current=S();return}if(!s){C.current=e;return}let w=e.messageCount>s.messageCount&&s.firstMessageKey!==null&&e.firstMessageKey!==s.firstMessageKey&&e.lastMessageKey===s.lastMessageKey,N=e.messageCount>s.messageCount&&s.lastMessageKey!==null&&e.lastMessageKey!==s.lastMessageKey&&e.firstMessageKey===s.firstMessageKey,F=e.messageCount>0&&s.messageCount>0&&e.firstMessageKey!==s.firstMessageKey&&e.lastMessageKey!==s.lastMessageKey;w?_(s):N?k.current&&K("auto"):F&&k.current&&K("auto"),C.current=S();},[t,x]);let L=()=>{let r=a.trim();if(r){if(!m){p?.();return}v(""),D(r);}},z=r=>{r.key==="Enter"&&!r.shiftKey&&(r.preventDefault(),L());};return t?jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx("style",{children:`
2
2
  :root {
3
3
  --chat-bg: #ffffff;
4
4
  }
5
5
  .dark {
6
6
  --chat-bg: #15181D;
7
7
  }
8
- `}),jsxRuntime.jsxs("div",{onClick:e=>e.stopPropagation(),className:p("fixed inset-0 z-[110] flex flex-col overflow-hidden bg-white dark:bg-app-dark-400 lg:inset-auto lg:right-6 lg:top-[75px] lg:max-w-[340px] lg:max-h-[calc(100vh-90px)] lg:border lg:border-black/10 lg:shadow-2xl lg:dark:border-white/10","lg:h-[800px]",l?.container),style:{background:N},children:[n?n():jsxRuntime.jsxs("div",{className:p("flex items-center justify-between border-b border-black/10 px-4 py-4 dark:border-white/10 lg:px-6",l?.header),children:[jsxRuntime.jsxs("div",{className:"flex items-center gap-3",children:[jsxRuntime.jsx("h2",{className:"text-lg font-semibold text-triad-dark-100 dark:text-white",children:x}),jsxRuntime.jsxs("div",{className:"flex items-center gap-1.5",children:[jsxRuntime.jsx("div",{className:"size-2 rounded-full bg-green-500 animate-pulse"}),jsxRuntime.jsxs("span",{className:"text-xs text-app-gray-400 dark:text-white",children:[M," online"]})]})]}),jsxRuntime.jsx("button",{onClick:h,children:jsxRuntime.jsx(lucideReact.X,{className:"text-black dark:text-white",size:18})})]}),jsxRuntime.jsxs("div",{className:p("flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6",l?.messages),children:[y.map((e,C)=>b?jsxRuntime.jsx(z__default.default.Fragment,{children:b(e)},C):jsxRuntime.jsxs("div",{className:`flex flex-col gap-1 ${e.authority===d?"items-end":"items-start"}`,children:[e.authority!==d&&jsxRuntime.jsx("span",{className:"max-w-[160px] truncate text-xs font-medium text-triad-dark-100 dark:text-white",children:e.name}),jsxRuntime.jsx("div",{className:p("max-w-[75%] rounded-2xl px-4 py-2.5",e.authority===d?"rounded-br-sm text-white":"rounded-bl-sm bg-black/5 text-triad-dark-100 dark:bg-white/5 dark:text-white"),style:e.authority===d?{backgroundColor:f}:void 0,children:jsxRuntime.jsx("p",{className:"text-sm break-words",children:e.message})}),jsxRuntime.jsx("span",{className:"text-[10px] text-black opacity-50 dark:text-white",children:dateFns.format(e.timestamp,"HH:mm")})]},C)),jsxRuntime.jsx("div",{ref:s})]}),jsxRuntime.jsx("div",{className:"border-t border-black/10 px-4 py-4 dark:border-white/10 lg:px-6",children:jsxRuntime.jsx("div",{className:"flex flex-col gap-2",children:jsxRuntime.jsxs("div",{className:"flex items-center gap-2",children:[jsxRuntime.jsx("input",{value:o,onChange:e=>g(e.target.value),onKeyDown:A,maxLength:K,placeholder:"Digite sua mensagem...",className:p("flex-1 rounded-xl bg-black/5 px-4 py-2.5 text-sm text-app-gray-400 outline-none dark:bg-white/5 dark:text-white",l?.input)}),jsxRuntime.jsx("button",{onClick:v,disabled:!o.trim()||!d&&!m,className:p("flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400",l?.button),style:{opacity:o.trim()?1:.5},children:jsxRuntime.jsx(lucideReact.Send,{color:c?.buttonSend.color,size:18})})]})})})]})]}):null}exports.ButtonWidget=W;exports.LiveChat=ie;//# sourceMappingURL=index.js.map
8
+ `}),jsxRuntime.jsx("div",{className:"fixed inset-0 z-[110]",onClick:u,"aria-hidden":"true",children:jsxRuntime.jsxs("div",{onClick:r=>r.stopPropagation(),role:"dialog","aria-modal":"true","aria-label":h,className:y("fixed inset-0 flex flex-col overflow-hidden bg-white dark:bg-app-dark-400 lg:inset-auto lg:right-6 lg:top-[75px] lg:max-w-[340px] lg:max-h-[calc(100vh-90px)] lg:border lg:border-black/10 lg:shadow-2xl lg:dark:border-white/10","lg:h-[800px]",d?.container),style:{background:P},children:[c?c():jsxRuntime.jsxs("div",{className:y("flex items-center justify-between border-b border-black/10 px-4 py-4 dark:border-white/10 lg:px-6",d?.header),children:[jsxRuntime.jsxs("div",{className:"flex items-center gap-3",children:[jsxRuntime.jsx("h2",{className:"text-lg font-semibold text-triad-dark-100 dark:text-white",children:h}),jsxRuntime.jsxs("div",{className:"flex items-center gap-1.5",children:[jsxRuntime.jsx("div",{className:"size-2 rounded-full bg-green-500 animate-pulse"}),jsxRuntime.jsxs("span",{className:"text-xs text-app-gray-400 dark:text-white",children:[B," online"]})]})]}),jsxRuntime.jsx("button",{onClick:u,children:jsxRuntime.jsx(lucideReact.X,{className:"text-black dark:text-white",size:18})})]}),jsxRuntime.jsx("div",{ref:o,onScroll:$,className:y("flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6",d?.messages),style:{overflowAnchor:"none"},children:(()=>{let r=new Map;return x.map(e=>{let s=H(e)??"message",w=r.get(s)??0,N=`${s}:${w}`;return r.set(s,w+1),M?jsxRuntime.jsx(X__default.default.Fragment,{children:M(e)},N):jsxRuntime.jsxs("div",{className:`flex flex-col gap-1 ${e.authority===m?"items-end":"items-start"}`,children:[e.authority!==m&&jsxRuntime.jsx("span",{className:"max-w-[160px] truncate text-xs font-medium text-triad-dark-100 dark:text-white",children:e.name}),jsxRuntime.jsx("div",{className:y("max-w-[75%] rounded-2xl px-4 py-2.5",e.authority===m?"rounded-br-sm text-white":"rounded-bl-sm bg-black/5 text-triad-dark-100 dark:bg-white/5 dark:text-white"),style:e.authority===m?{backgroundColor:E}:void 0,children:jsxRuntime.jsx("p",{className:"text-sm break-words",children:e.message})}),jsxRuntime.jsxs("div",{className:"flex items-center gap-2 text-[10px] text-black opacity-50 dark:text-white",children:[jsxRuntime.jsx("span",{children:re(e.predictionsCount)}),jsxRuntime.jsx("span",{children:dateFns.format(e.timestamp,"HH:mm")})]})]},N)})})()}),jsxRuntime.jsx("div",{className:"border-t border-black/10 px-4 py-4 dark:border-white/10 lg:px-6",children:jsxRuntime.jsx("div",{className:"flex flex-col gap-2",children:jsxRuntime.jsxs("div",{className:"flex items-center gap-2",children:[jsxRuntime.jsx("input",{value:a,onChange:r=>v(r.target.value),onKeyDown:z,maxLength:Z,placeholder:"Digite sua mensagem...",className:y("flex-1 rounded-xl bg-black/5 px-4 py-2.5 text-sm text-app-gray-400 outline-none dark:bg-white/5 dark:text-white",d?.input)}),jsxRuntime.jsx("button",{onClick:L,disabled:!a.trim()||!m&&!p,className:y("flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400",d?.button),style:{opacity:a.trim()?1:.5},children:jsxRuntime.jsx(lucideReact.Send,{color:g?.buttonSend.color,size:18})})]})})})]})})]}):null}exports.ButtonWidget=ne;exports.LiveChat=Ce;//# sourceMappingURL=index.js.map
9
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/ButtonWidget.tsx","../src/utils/cn.ts","../src/utils/socket.ts","../src/hooks/useChat.ts","../src/components/LiveChat.tsx"],"names":["ButtonWidget","label","variant","className","props","jsx","cn","classes","twMerge","clsx","socket","io","socket_default","useChat","authority","customerAuthority","messages","setMessages","useState","onlineCount","setOnlineCount","normalizedCustomerAuthority","useEffect","handleHistory","msgs","handleMessage","msg","prev","handleUsers","data","joinChat","joinPayload","res","message","resolve","CHAT_MAX_MESSAGE_LENGTH","LiveChat","isOpen","onClose","title","theme","classNames","renderMessage","renderHeader","onConnectWallet","inputValue","setInputValue","messagesEndRef","useRef","primary","background","sendMessage","scrollToBottom","handleSendMessage","handleKeyDown","jsxs","Fragment","X","idx","React","format","Send"],"mappings":"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","file":"index.js","sourcesContent":["'use client';\n\nimport React from 'react';\n\nexport interface ButtonWidgetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n variant?: 'primary' | 'secondary';\n}\n\nexport const ButtonWidget: React.FC<ButtonWidgetProps> = ({ \n label, \n variant = 'primary', \n className = '',\n ...props \n}) => {\n const baseStyles = 'px-4 py-2 rounded-lg font-medium transition-colors';\n const variants = {\n primary: 'bg-blue-600 text-white hover:bg-blue-700',\n secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300'\n };\n\n return (\n <button \n className={`${baseStyles} ${variants[variant]} ${className}`}\n {...props}\n >\n {label}\n </button>\n );\n};\n","import type { ClassValue } from \"clsx\";\nimport { clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...classes: ClassValue[]) {\n return twMerge(clsx(classes));\n}\n","import io from \"socket.io-client\";\n\nconst socket = io(\"https://beta.triadfi.co\", {\n path: \"/socket.io\",\n reconnection: true,\n reconnectionAttempts: 5,\n reconnectionDelay: 1000,\n randomizationFactor: 0.5,\n timeout: 5000,\n transports: [\"websocket\"],\n});\n\nexport default socket;\n","import socket from \"@/utils/socket\";\nimport { useEffect, useState } from \"react\";\n\nexport interface ChatMessage {\n authority?: string;\n name: string;\n image: string;\n predictionsCount: number;\n message: string;\n timestamp: number;\n}\n\ninterface ChatUsersPayload {\n count: number;\n}\n\ninterface ChatResponse {\n error: string | null;\n}\n\nexport function useChat({\n authority,\n customerAuthority,\n}: {\n authority?: string;\n customerAuthority: string;\n}) {\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [onlineCount, setOnlineCount] = useState(0);\n const normalizedCustomerAuthority = customerAuthority?.trim();\n\n useEffect(() => {\n if (!normalizedCustomerAuthority) return;\n\n const handleHistory = (msgs: ChatMessage[]) => {\n setMessages(msgs);\n };\n\n const handleMessage = (msg: ChatMessage) => {\n setMessages((prev) => [...prev, msg]);\n };\n\n const handleUsers = (data: ChatUsersPayload) => {\n setOnlineCount(data.count);\n };\n\n const joinChat = () => {\n const joinPayload = authority\n ? { authority, customerAuthority: normalizedCustomerAuthority }\n : { customerAuthority: normalizedCustomerAuthority };\n\n socket.emit(\"chat:join\", joinPayload, (res: ChatResponse) => {\n if (res?.error) {\n console.error(\"join error\", res.error);\n }\n });\n };\n\n socket.on(\"chat:history\", handleHistory);\n socket.on(\"chat:message\", handleMessage);\n socket.on(\"chat:users\", handleUsers);\n socket.on(\"connect\", joinChat);\n joinChat();\n\n return () => {\n socket.off(\"chat:history\", handleHistory);\n socket.off(\"chat:message\", handleMessage);\n socket.off(\"chat:users\", handleUsers);\n socket.off(\"connect\", joinChat);\n };\n }, [authority, normalizedCustomerAuthority]);\n\n const sendMessage = (message: string): Promise<ChatResponse> =>\n new Promise((resolve) => {\n if (!authority || !normalizedCustomerAuthority) {\n resolve({ error: \"INVALID_REQUEST\" });\n return;\n }\n\n socket.emit(\n \"chat:message\",\n { authority, customerAuthority: normalizedCustomerAuthority, message },\n (res: ChatResponse) => {\n if (res?.error) {\n console.error(\"send error\", res.error);\n }\n resolve(res ?? { error: \"INTERNAL_ERROR\" });\n },\n );\n });\n\n return {\n messages,\n onlineCount,\n sendMessage,\n };\n}\n","\"use client\";\n\nimport React, { useState, useRef, useEffect } from \"react\";\nimport { X, Send } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport { useChat, ChatMessage } from \"@/hooks/useChat\";\nimport { format } from \"date-fns\";\n\nexport interface LiveChatTheme {\n primaryColor?: string;\n background?: string;\n textColor?: string;\n buttonSend: {\n color: string;\n } \n}\n\nexport interface LiveChatClassNames {\n container?: string;\n header?: string;\n messages?: string;\n input?: string;\n button?: string;\n}\n\nexport interface LiveChatProps {\n isOpen: boolean;\n onClose: () => void;\n\n // ui\n title?: string;\n\n // customization\n theme?: LiveChatTheme;\n classNames?: LiveChatClassNames;\n\n // slots\n renderMessage?: (message: ChatMessage) => React.ReactNode;\n renderHeader?: () => React.ReactNode;\n authority: string | null;\n customerAuthority: string;\n onConnectWallet?: () => void;\n fallbackMessages?: {\n connectWallet: string;\n description: string;\n };\n}\n\nconst CHAT_MAX_MESSAGE_LENGTH = 200;\n\nexport function LiveChat({\n isOpen,\n onClose,\n title = \"Live Chat\",\n theme,\n classNames,\n renderMessage,\n renderHeader,\n authority,\n customerAuthority,\n onConnectWallet,\n}: LiveChatProps) {\n const [inputValue, setInputValue] = useState(\"\");\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const primary = theme?.primaryColor || \"#FF3D00\";\n const background = theme?.background || \"var(--chat-bg)\";\n\n const { sendMessage, onlineCount, messages } = useChat({\n authority: authority || undefined,\n customerAuthority: customerAuthority,\n });\n\n const scrollToBottom = () => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n };\n\n useEffect(() => {\n scrollToBottom();\n }, [messages]);\n\n const handleSendMessage = () => {\n const message = inputValue.trim();\n if (!message) return;\n\n if (!authority) {\n onConnectWallet?.();\n return;\n }\n\n setInputValue(\"\");\n sendMessage(message);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSendMessage();\n }\n };\n\n if (!isOpen) return null;\n\n return (\n <>\n <style>{`\n :root {\n --chat-bg: #ffffff;\n }\n .dark {\n --chat-bg: #15181D;\n }\n `}</style>\n\n <div\n onClick={(e) => e.stopPropagation()}\n className={cn(\n \"fixed inset-0 z-[110] flex flex-col overflow-hidden bg-white dark:bg-app-dark-400 lg:inset-auto lg:right-6 lg:top-[75px] lg:max-w-[340px] lg:max-h-[calc(100vh-90px)] lg:border lg:border-black/10 lg:shadow-2xl lg:dark:border-white/10\",\n \"lg:h-[800px]\",\n classNames?.container,\n )}\n style={{ background }}\n >\n {renderHeader ? (\n renderHeader()\n ) : (\n <div\n className={cn(\n \"flex items-center justify-between border-b border-black/10 px-4 py-4 dark:border-white/10 lg:px-6\",\n classNames?.header,\n )}\n >\n <div className=\"flex items-center gap-3\">\n <h2 className=\"text-lg font-semibold text-triad-dark-100 dark:text-white\">\n {title}\n </h2>\n <div className=\"flex items-center gap-1.5\">\n <div className=\"size-2 rounded-full bg-green-500 animate-pulse\" />\n <span className=\"text-xs text-app-gray-400 dark:text-white\">\n {onlineCount} online\n </span>\n </div>\n </div>\n <button onClick={onClose}>\n <X className=\"text-black dark:text-white\" size={18} />\n </button>\n </div>\n )}\n\n <div\n className={cn(\n \"flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6\",\n classNames?.messages,\n )}\n >\n {messages.map((message, idx) =>\n renderMessage ? (\n <React.Fragment key={idx}>{renderMessage(message)}</React.Fragment>\n ) : (\n <div\n key={idx}\n className={`flex flex-col gap-1 ${\n message.authority === authority ? \"items-end\" : \"items-start\"\n }`}\n >\n {message.authority !== authority && (\n <span className=\"max-w-[160px] truncate text-xs font-medium text-triad-dark-100 dark:text-white\">\n {message.name}\n </span>\n )}\n\n <div\n className={cn(\n \"max-w-[75%] rounded-2xl px-4 py-2.5\",\n message.authority === authority\n ? \"rounded-br-sm text-white\"\n : \"rounded-bl-sm bg-black/5 text-triad-dark-100 dark:bg-white/5 dark:text-white\",\n )}\n style={\n message.authority === authority\n ? { backgroundColor: primary }\n : undefined\n }\n >\n <p className=\"text-sm break-words\">{message.message}</p>\n </div>\n\n <span className=\"text-[10px] text-black opacity-50 dark:text-white\">\n {format(message.timestamp, \"HH:mm\")}\n </span>\n </div>\n ),\n )}\n <div ref={messagesEndRef} />\n </div>\n\n <div className=\"border-t border-black/10 px-4 py-4 dark:border-white/10 lg:px-6\">\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-center gap-2\">\n <input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n maxLength={CHAT_MAX_MESSAGE_LENGTH}\n placeholder=\"Digite sua mensagem...\"\n className={cn(\n \"flex-1 rounded-xl bg-black/5 px-4 py-2.5 text-sm text-app-gray-400 outline-none dark:bg-white/5 dark:text-white\",\n classNames?.input,\n )}\n />\n\n <button\n onClick={handleSendMessage}\n disabled={!inputValue.trim() || (!authority && !onConnectWallet)}\n className={cn(\n \"flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400\",\n classNames?.button,\n )}\n style={{\n opacity: inputValue.trim() ? 1 : 0.5,\n }}\n >\n <Send color={theme?.buttonSend.color} size={18} />\n </button>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/components/ButtonWidget.tsx","../src/utils/cn.ts","../src/utils/socket.ts","../src/hooks/useChat.ts","../src/components/LiveChat.tsx"],"names":["ButtonWidget","label","variant","className","props","jsx","cn","classes","twMerge","clsx","socket","io","socket_default","useChat","authority","customerAuthority","messages","setMessages","useState","onlineCount","setOnlineCount","normalizedCustomerAuthority","useEffect","handleHistory","msgs","handleMessage","msg","prev","handleUsers","data","joinChat","joinPayload","res","message","resolve","CHAT_MAX_MESSAGE_LENGTH","AUTO_SCROLL_THRESHOLD_PX","getMessageIdentity","isNearBottom","element","threshold","formatPredictionsCount","count","LiveChat","isOpen","onClose","title","theme","classNames","renderMessage","renderHeader","onConnectWallet","inputValue","setInputValue","messagesContainerRef","useRef","hasInitialScrollRef","shouldAutoScrollRef","previousSnapshotRef","primary","background","sendMessage","createMessagesSnapshot","container","scrollToBottom","behavior","preserveScrollPositionAfterPrepend","previousSnapshot","scrollHeightDelta","handleMessagesScroll","useLayoutEffect","nextSnapshot","hasPrependedMessages","hasAppendedMessages","hasReplacedHistory","handleSendMessage","handleKeyDown","e","jsxs","Fragment","X","messageOccurrences","baseKey","occurrence","messageKey","React","format","Send"],"mappings":"sYASO,IAAMA,GAA4C,CAAC,CACxD,MAAAC,CAAAA,CACA,OAAA,CAAAC,EAAU,SAAA,CACV,SAAA,CAAAC,EAAY,EAAA,CACZ,GAAGC,CACL,CAAA,GAQIC,cAAAA,CAAC,UACC,SAAA,CAAW,CAAA,mDAAA,EAPE,CACf,OAAA,CAAS,2CACT,SAAA,CAAW,6CACb,EAIyCH,CAAO,CAAC,IAAIC,CAAS,CAAA,CAAA,CACzD,GAAGC,CAAAA,CAEH,QAAA,CAAAH,EACH,ECvBG,SAASK,KAAMC,CAAAA,CAAuB,CAC3C,OAAOC,qBAAAA,CAAQC,SAAAA,CAAKF,CAAO,CAAC,CAC9B,CCJA,IAAMG,EAASC,kBAAAA,CAAG,yBAAA,CAA2B,CAC3C,IAAA,CAAM,YAAA,CACN,aAAc,IAAA,CACd,oBAAA,CAAsB,EACtB,iBAAA,CAAmB,GAAA,CACnB,oBAAqB,EAAA,CACrB,OAAA,CAAS,GAAA,CACT,UAAA,CAAY,CAAC,WAAW,CAC1B,CAAC,CAAA,CAEMC,CAAAA,CAAQF,ECQR,SAASG,CAAAA,CAAQ,CACtB,SAAA,CAAAC,CAAAA,CACA,kBAAAC,CACF,CAAA,CAGG,CACD,GAAM,CAACC,CAAAA,CAAUC,CAAW,EAAIC,UAAAA,CAAwB,EAAE,CAAA,CACpD,CAACC,EAAaC,CAAc,CAAA,CAAIF,WAAS,CAAC,CAAA,CAC1CG,EAA8BN,CAAAA,EAAmB,IAAA,GAEvD,OAAAO,WAAAA,CAAU,IAAM,CACd,GAAI,CAACD,CAAAA,CAA6B,OAElC,IAAME,CAAAA,CAAiBC,GAAwB,CAC7CP,CAAAA,CAAYO,CAAI,EAClB,CAAA,CAEMC,EAAiBC,CAAAA,EAAqB,CAC1CT,EAAaU,CAAAA,EAAS,CAAC,GAAGA,CAAAA,CAAMD,CAAG,CAAC,EACtC,CAAA,CAEME,EAAeC,CAAAA,EAA2B,CAC9CT,EAAeS,CAAAA,CAAK,KAAK,EAC3B,CAAA,CAEMC,CAAAA,CAAW,IAAM,CACrB,IAAMC,EAAcjB,CAAAA,CAChB,CAAE,UAAAA,CAAAA,CAAW,iBAAA,CAAmBO,CAA4B,CAAA,CAC5D,CAAE,iBAAA,CAAmBA,CAA4B,EAErDT,CAAAA,CAAO,IAAA,CAAK,YAAamB,CAAAA,CAAcC,CAAAA,EAAsB,CACvDA,CAAAA,EAAK,KAAA,EACP,QAAQ,KAAA,CAAM,YAAA,CAAcA,EAAI,KAAK,EAEzC,CAAC,EACH,CAAA,CAEA,OAAApB,CAAAA,CAAO,EAAA,CAAG,cAAA,CAAgBW,CAAa,EACvCX,CAAAA,CAAO,EAAA,CAAG,eAAgBa,CAAa,CAAA,CACvCb,EAAO,EAAA,CAAG,YAAA,CAAcgB,CAAW,CAAA,CACnChB,CAAAA,CAAO,GAAG,SAAA,CAAWkB,CAAQ,EAC7BA,CAAAA,EAAS,CAEF,IAAM,CACXlB,CAAAA,CAAO,GAAA,CAAI,cAAA,CAAgBW,CAAa,CAAA,CACxCX,CAAAA,CAAO,IAAI,cAAA,CAAgBa,CAAa,EACxCb,CAAAA,CAAO,GAAA,CAAI,aAAcgB,CAAW,CAAA,CACpChB,EAAO,GAAA,CAAI,SAAA,CAAWkB,CAAQ,EAChC,CACF,EAAG,CAAChB,CAAAA,CAAWO,CAA2B,CAAC,EAqBpC,CACL,QAAA,CAAAL,EACA,WAAA,CAAAG,CAAAA,CACA,YAtBmBc,CAAAA,EACnB,IAAI,QAASC,CAAAA,EAAY,CACvB,GAAI,CAACpB,CAAAA,EAAa,CAACO,CAAAA,CAA6B,CAC9Ca,EAAQ,CAAE,KAAA,CAAO,iBAAkB,CAAC,EACpC,MACF,CAEAtB,EAAO,IAAA,CACL,cAAA,CACA,CAAE,SAAA,CAAAE,CAAAA,CAAW,kBAAmBO,CAAAA,CAA6B,OAAA,CAAAY,CAAQ,CAAA,CACpED,CAAAA,EAAsB,CACjBA,CAAAA,EAAK,KAAA,EACP,QAAQ,KAAA,CAAM,YAAA,CAAcA,CAAAA,CAAI,KAAK,EAEvCE,CAAAA,CAAQF,CAAAA,EAAO,CAAE,KAAA,CAAO,gBAAiB,CAAC,EAC5C,CACF,EACF,CAAC,CAMH,CACF,CChDA,IAAMG,EAA0B,GAAA,CAC1BC,EAAAA,CAA2B,GASjC,SAASC,CAAAA,CAAmBJ,EAAuB,CACjD,OAAKA,EAEE,CACLA,CAAAA,CAAQ,UACRA,CAAAA,CAAQ,SAAA,EAAa,YACrBA,CAAAA,CAAQ,IAAA,CACRA,EAAQ,OACV,CAAA,CAAE,KAAK,GAAG,CAAA,CAPW,IAQvB,CAEA,SAASK,GAAaC,CAAAA,CAAyBC,CAAAA,CAAYJ,GAA0B,CAInF,OAFEG,EAAQ,YAAA,CAAeA,CAAAA,CAAQ,UAAYA,CAAAA,CAAQ,YAAA,EAE1BC,CAC7B,CAEA,SAASC,EAAAA,CAAuBC,CAAAA,CAAe,CAC7C,OAAO,CAAA,EAAGA,CAAK,CAAA,CAAA,EAAIA,CAAAA,GAAU,EAAI,UAAA,CAAa,WAAW,EAC3D,CAEO,SAASC,GAAS,CACvB,MAAA,CAAAC,EACA,OAAA,CAAAC,CAAAA,CACA,MAAAC,CAAAA,CAAQ,WAAA,CACR,KAAA,CAAAC,CAAAA,CACA,WAAAC,CAAAA,CACA,aAAA,CAAAC,EACA,YAAA,CAAAC,CAAAA,CACA,UAAApC,CAAAA,CACA,iBAAA,CAAAC,EACA,eAAA,CAAAoC,CACF,EAAkB,CAChB,GAAM,CAACC,CAAAA,CAAYC,CAAa,EAAInC,UAAAA,CAAS,EAAE,CAAA,CACzCoC,CAAAA,CAAuBC,SAAuB,IAAI,CAAA,CAClDC,EAAsBD,QAAAA,CAAO,KAAK,EAClCE,CAAAA,CAAsBF,QAAAA,CAAO,IAAI,CAAA,CACjCG,CAAAA,CAAsBH,SAAgC,IAAI,CAAA,CAC1DI,EAAUZ,CAAAA,EAAO,YAAA,EAAgB,UACjCa,CAAAA,CAAab,CAAAA,EAAO,UAAA,EAAc,gBAAA,CAElC,CAAE,WAAA,CAAAc,CAAAA,CAAa,YAAA1C,CAAAA,CAAa,QAAA,CAAAH,CAAS,CAAA,CAAIH,CAAAA,CAAQ,CACrD,SAAA,CAAWC,CAAAA,EAAa,OACxB,iBAAA,CAAmBC,CACrB,CAAC,CAAA,CAEK+C,CAAAA,CAAyB,IAAwB,CACrD,IAAMC,CAAAA,CAAYT,CAAAA,CAAqB,QAEvC,OAAO,CACL,gBAAiBjB,CAAAA,CAAmBrB,CAAAA,CAAS,CAAC,CAAC,CAAA,CAC/C,eAAgBqB,CAAAA,CAAmBrB,CAAAA,CAASA,EAAS,MAAA,CAAS,CAAC,CAAC,CAAA,CAChE,YAAA,CAAcA,EAAS,MAAA,CACvB,YAAA,CAAc+C,CAAAA,EAAW,YAAA,EAAgB,CAC3C,CACF,CAAA,CAEMC,EAAiB,CAACC,CAAAA,CAA2B,SAAW,CAC5D,IAAMF,EAAYT,CAAAA,CAAqB,OAAA,CAClCS,IAELA,CAAAA,CAAU,QAAA,CAAS,CACjB,GAAA,CAAKA,CAAAA,CAAU,aACf,QAAA,CAAAE,CACF,CAAC,CAAA,CAEDR,EAAoB,OAAA,CAAU,IAAA,EAChC,EAEMS,CAAAA,CACJC,CAAAA,EACG,CACH,IAAMJ,CAAAA,CAAYT,EAAqB,OAAA,CACvC,GAAI,CAACS,CAAAA,CAAW,OAEhB,IAAMK,CAAAA,CAAoBL,CAAAA,CAAU,aAAeI,CAAAA,CAAiB,YAAA,CAEhEC,IAAsB,CAAA,GACxBL,CAAAA,CAAU,WAAaK,CAAAA,EAE3B,CAAA,CAEMC,EAAuB,IAAM,CACjC,IAAMN,CAAAA,CAAYT,CAAAA,CAAqB,QAClCS,CAAAA,GAELN,CAAAA,CAAoB,QAAUnB,EAAAA,CAAayB,CAAS,GACtD,CAAA,CAEAO,iBAAAA,CAAgB,IAAM,CACpB,GAAI,CAAC1B,CAAAA,CAAQ,CACXY,CAAAA,CAAoB,OAAA,CAAU,MAC9BC,CAAAA,CAAoB,OAAA,CAAU,KAC9BC,CAAAA,CAAoB,OAAA,CAAU,KAC9B,MACF,CAGA,GAAI,CADcJ,CAAAA,CAAqB,QACvB,OAEhB,IAAMiB,EAAeT,CAAAA,EAAuB,CACtCK,CAAAA,CAAmBT,CAAAA,CAAoB,QAE7C,GAAI,CAACF,EAAoB,OAAA,CAAS,CAChC,GAAIxC,CAAAA,CAAS,MAAA,GAAW,EAAG,CACzB0C,CAAAA,CAAoB,QAAUa,CAAAA,CAC9B,MACF,CAEAP,CAAAA,CAAe,MAAM,EACrBR,CAAAA,CAAoB,OAAA,CAAU,IAAA,CAC9BE,CAAAA,CAAoB,QAAUI,CAAAA,EAAuB,CACrD,MACF,CAEA,GAAI,CAACK,CAAAA,CAAkB,CACrBT,EAAoB,OAAA,CAAUa,CAAAA,CAC9B,MACF,CAEA,IAAMC,EACJD,CAAAA,CAAa,YAAA,CAAeJ,EAAiB,YAAA,EAC7CA,CAAAA,CAAiB,kBAAoB,IAAA,EACrCI,CAAAA,CAAa,kBAAoBJ,CAAAA,CAAiB,eAAA,EAClDI,EAAa,cAAA,GAAmBJ,CAAAA,CAAiB,eAE7CM,CAAAA,CACJF,CAAAA,CAAa,aAAeJ,CAAAA,CAAiB,YAAA,EAC7CA,EAAiB,cAAA,GAAmB,IAAA,EACpCI,EAAa,cAAA,GAAmBJ,CAAAA,CAAiB,gBACjDI,CAAAA,CAAa,eAAA,GAAoBJ,CAAAA,CAAiB,eAAA,CAE9CO,EACJH,CAAAA,CAAa,YAAA,CAAe,GAC5BJ,CAAAA,CAAiB,YAAA,CAAe,GAChCI,CAAAA,CAAa,eAAA,GAAoBJ,EAAiB,eAAA,EAClDI,CAAAA,CAAa,iBAAmBJ,CAAAA,CAAiB,cAAA,CAE/CK,EACFN,CAAAA,CAAmCC,CAAgB,EAC1CM,CAAAA,CACLhB,CAAAA,CAAoB,OAAA,EACtBO,CAAAA,CAAe,MAAM,CAAA,CAEdU,CAAAA,EAAsBjB,EAAoB,OAAA,EACnDO,CAAAA,CAAe,MAAM,CAAA,CAGvBN,CAAAA,CAAoB,QAAUI,CAAAA,GAChC,EAAG,CAAClB,CAAAA,CAAQ5B,CAAQ,CAAC,CAAA,CAErB,IAAM2D,CAAAA,CAAoB,IAAM,CAC9B,IAAM1C,EAAUmB,CAAAA,CAAW,IAAA,GAC3B,GAAKnB,CAAAA,CAEL,IAAI,CAACnB,CAAAA,CAAW,CACdqC,CAAAA,IAAkB,CAClB,MACF,CAEAE,CAAAA,CAAc,EAAE,CAAA,CAChBQ,CAAAA,CAAY5B,CAAO,EAAA,CACrB,CAAA,CAEM2C,EAAiBC,CAAAA,EAA2B,CAC5CA,EAAE,GAAA,GAAQ,OAAA,EAAW,CAACA,CAAAA,CAAE,QAAA,GAC1BA,EAAE,cAAA,EAAe,CACjBF,GAAkB,EAEtB,CAAA,CAEA,OAAK/B,CAAAA,CAGHkC,eAAAA,CAAAC,oBAAA,CACE,QAAA,CAAA,CAAA1E,eAAC,OAAA,CAAA,CAAO,QAAA,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAA,CAAA,CAON,EAEFA,cAAAA,CAAC,KAAA,CAAA,CACC,SAAA,CAAU,uBAAA,CACV,QAASwC,CAAAA,CACT,aAAA,CAAY,MAAA,CAEZ,QAAA,CAAAiC,gBAAC,KAAA,CAAA,CACC,OAAA,CAAUD,CAAAA,EAAMA,CAAAA,CAAE,iBAAgB,CAClC,IAAA,CAAK,QAAA,CACL,YAAA,CAAW,OACX,YAAA,CAAY/B,CAAAA,CACZ,SAAA,CAAWxC,CAAAA,CACT,mOACA,cAAA,CACA0C,CAAAA,EAAY,SACd,CAAA,CACA,MAAO,CAAE,UAAA,CAAAY,CAAW,CAAA,CAEnB,QAAA,CAAA,CAAAV,EACCA,CAAAA,EAAa,CAEb4B,eAAAA,CAAC,KAAA,CAAA,CACC,UAAWxE,CAAAA,CACT,mGAAA,CACA0C,CAAAA,EAAY,MACd,EAEA,QAAA,CAAA,CAAA8B,eAAAA,CAAC,KAAA,CAAA,CAAI,SAAA,CAAU,0BACb,QAAA,CAAA,CAAAzE,cAAAA,CAAC,IAAA,CAAA,CAAG,SAAA,CAAU,4DACX,QAAA,CAAAyC,CAAAA,CACH,CAAA,CACAgC,eAAAA,CAAC,OAAI,SAAA,CAAU,2BAAA,CACb,QAAA,CAAA,CAAAzE,cAAAA,CAAC,OAAI,SAAA,CAAU,gDAAA,CAAiD,CAAA,CAChEyE,eAAAA,CAAC,QAAK,SAAA,CAAU,2CAAA,CACb,UAAA3D,CAAAA,CAAY,SAAA,CAAA,CACf,GACF,CAAA,CAAA,CACF,CAAA,CACAd,cAAAA,CAAC,QAAA,CAAA,CAAO,QAASwC,CAAAA,CACf,QAAA,CAAAxC,cAAAA,CAAC2E,aAAAA,CAAA,CAAE,SAAA,CAAU,4BAAA,CAA6B,IAAA,CAAM,EAAA,CAAI,EACtD,CAAA,CAAA,CACF,CAAA,CAGF3E,cAAAA,CAAC,KAAA,CAAA,CACC,IAAKiD,CAAAA,CACL,QAAA,CAAUe,CAAAA,CACV,SAAA,CAAW/D,EACT,oDAAA,CACA0C,CAAAA,EAAY,QACd,CAAA,CACA,MAAO,CAAE,cAAA,CAAgB,MAAO,CAAA,CAE9B,cAAM,CACN,IAAMiC,EAAqB,IAAI,GAAA,CAE/B,OAAOjE,CAAAA,CAAS,GAAA,CAAKiB,CAAAA,EAAY,CAC/B,IAAMiD,CAAAA,CAAU7C,CAAAA,CAAmBJ,CAAO,CAAA,EAAK,UACzCkD,CAAAA,CAAaF,CAAAA,CAAmB,GAAA,CAAIC,CAAO,GAAK,CAAA,CAChDE,CAAAA,CAAa,GAAGF,CAAO,CAAA,CAAA,EAAIC,CAAU,CAAA,CAAA,CAE3C,OAAAF,CAAAA,CAAmB,GAAA,CAAIC,EAASC,CAAAA,CAAa,CAAC,CAAA,CAEvClC,CAAAA,CACL5C,eAACgF,kBAAAA,CAAM,QAAA,CAAN,CACE,QAAA,CAAApC,EAAchB,CAAO,CAAA,CAAA,CADHmD,CAErB,CAAA,CAEAN,eAAAA,CAAC,OAEC,SAAA,CAAW,CAAA,oBAAA,EACT7C,CAAAA,CAAQ,SAAA,GAAcnB,EAAY,WAAA,CAAc,aAClD,CAAA,CAAA,CAEC,QAAA,CAAA,CAAAmB,EAAQ,SAAA,GAAcnB,CAAAA,EACrBT,cAAAA,CAAC,MAAA,CAAA,CAAK,UAAU,gFAAA,CACb,QAAA,CAAA4B,CAAAA,CAAQ,IAAA,CACX,EAGF5B,cAAAA,CAAC,KAAA,CAAA,CACC,SAAA,CAAWC,CAAAA,CACT,sCACA2B,CAAAA,CAAQ,SAAA,GAAcnB,CAAAA,CAClB,0BAAA,CACA,8EACN,CAAA,CACA,KAAA,CACEmB,CAAAA,CAAQ,SAAA,GAAcnB,EAClB,CAAE,eAAA,CAAiB6C,CAAQ,CAAA,CAC3B,MAAA,CAGN,SAAAtD,cAAAA,CAAC,GAAA,CAAA,CAAE,SAAA,CAAU,qBAAA,CAAuB,SAAA4B,CAAAA,CAAQ,OAAA,CAAQ,CAAA,CACtD,CAAA,CAEA6C,gBAAC,KAAA,CAAA,CAAI,SAAA,CAAU,2EAAA,CACb,QAAA,CAAA,CAAAzE,eAAC,MAAA,CAAA,CAAM,QAAA,CAAAoC,EAAAA,CAAuBR,CAAAA,CAAQ,gBAAgB,CAAA,CAAE,CAAA,CACxD5B,cAAAA,CAAC,MAAA,CAAA,CAAM,SAAAiF,cAAAA,CAAOrD,CAAAA,CAAQ,SAAA,CAAW,OAAO,EAAE,CAAA,CAAA,CAC5C,CAAA,CAAA,CAAA,CA9BKmD,CA+BP,CAEJ,CAAC,CACH,CAAA,IACF,CAAA,CAEA/E,cAAAA,CAAC,OAAI,SAAA,CAAU,iEAAA,CACb,QAAA,CAAAA,cAAAA,CAAC,OAAI,SAAA,CAAU,qBAAA,CACb,QAAA,CAAAyE,eAAAA,CAAC,OAAI,SAAA,CAAU,yBAAA,CACb,QAAA,CAAA,CAAAzE,cAAAA,CAAC,SACC,KAAA,CAAO+C,CAAAA,CACP,QAAA,CAAWyB,CAAAA,EAAMxB,EAAcwB,CAAAA,CAAE,MAAA,CAAO,KAAK,CAAA,CAC7C,UAAWD,CAAAA,CACX,SAAA,CAAWzC,CAAAA,CACX,WAAA,CAAY,yBACZ,SAAA,CAAW7B,CAAAA,CACT,iHAAA,CACA0C,CAAAA,EAAY,KACd,CAAA,CACF,CAAA,CAEA3C,eAAC,QAAA,CAAA,CACC,OAAA,CAASsE,EACT,QAAA,CAAU,CAACvB,CAAAA,CAAW,IAAA,IAAW,CAACtC,CAAAA,EAAa,CAACqC,CAAAA,CAChD,UAAW7C,CAAAA,CACT,uFAAA,CACA0C,CAAAA,EAAY,MACd,EACA,KAAA,CAAO,CACL,QAASI,CAAAA,CAAW,IAAA,GAAS,CAAA,CAAI,EACnC,CAAA,CAEA,QAAA,CAAA/C,eAACkF,gBAAAA,CAAA,CAAK,KAAA,CAAOxC,CAAAA,EAAO,WAAW,KAAA,CAAO,IAAA,CAAM,EAAA,CAAI,CAAA,CAClD,GACF,CAAA,CACF,CAAA,CACF,GACF,CAAA,CACF,CAAA,CAAA,CACF,EAvJkB,IAyJtB","file":"index.js","sourcesContent":["'use client';\n\nimport React from 'react';\n\nexport interface ButtonWidgetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n variant?: 'primary' | 'secondary';\n}\n\nexport const ButtonWidget: React.FC<ButtonWidgetProps> = ({ \n label, \n variant = 'primary', \n className = '',\n ...props \n}) => {\n const baseStyles = 'px-4 py-2 rounded-lg font-medium transition-colors';\n const variants = {\n primary: 'bg-blue-600 text-white hover:bg-blue-700',\n secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300'\n };\n\n return (\n <button \n className={`${baseStyles} ${variants[variant]} ${className}`}\n {...props}\n >\n {label}\n </button>\n );\n};\n","import type { ClassValue } from \"clsx\";\nimport { clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...classes: ClassValue[]) {\n return twMerge(clsx(classes));\n}\n","import io from \"socket.io-client\";\n\nconst socket = io(\"https://beta.triadfi.co\", {\n path: \"/socket.io\",\n reconnection: true,\n reconnectionAttempts: 5,\n reconnectionDelay: 1000,\n randomizationFactor: 0.5,\n timeout: 5000,\n transports: [\"websocket\"],\n});\n\nexport default socket;\n","import socket from \"@/utils/socket\";\nimport { useEffect, useState } from \"react\";\n\nexport interface ChatMessage {\n authority?: string;\n name: string;\n image: string;\n predictionsCount: number;\n message: string;\n timestamp: number;\n}\n\ninterface ChatUsersPayload {\n count: number;\n}\n\ninterface ChatResponse {\n error: string | null;\n}\n\nexport function useChat({\n authority,\n customerAuthority,\n}: {\n authority?: string;\n customerAuthority: string;\n}) {\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [onlineCount, setOnlineCount] = useState(0);\n const normalizedCustomerAuthority = customerAuthority?.trim();\n\n useEffect(() => {\n if (!normalizedCustomerAuthority) return;\n\n const handleHistory = (msgs: ChatMessage[]) => {\n setMessages(msgs);\n };\n\n const handleMessage = (msg: ChatMessage) => {\n setMessages((prev) => [...prev, msg]);\n };\n\n const handleUsers = (data: ChatUsersPayload) => {\n setOnlineCount(data.count);\n };\n\n const joinChat = () => {\n const joinPayload = authority\n ? { authority, customerAuthority: normalizedCustomerAuthority }\n : { customerAuthority: normalizedCustomerAuthority };\n\n socket.emit(\"chat:join\", joinPayload, (res: ChatResponse) => {\n if (res?.error) {\n console.error(\"join error\", res.error);\n }\n });\n };\n\n socket.on(\"chat:history\", handleHistory);\n socket.on(\"chat:message\", handleMessage);\n socket.on(\"chat:users\", handleUsers);\n socket.on(\"connect\", joinChat);\n joinChat();\n\n return () => {\n socket.off(\"chat:history\", handleHistory);\n socket.off(\"chat:message\", handleMessage);\n socket.off(\"chat:users\", handleUsers);\n socket.off(\"connect\", joinChat);\n };\n }, [authority, normalizedCustomerAuthority]);\n\n const sendMessage = (message: string): Promise<ChatResponse> =>\n new Promise((resolve) => {\n if (!authority || !normalizedCustomerAuthority) {\n resolve({ error: \"INVALID_REQUEST\" });\n return;\n }\n\n socket.emit(\n \"chat:message\",\n { authority, customerAuthority: normalizedCustomerAuthority, message },\n (res: ChatResponse) => {\n if (res?.error) {\n console.error(\"send error\", res.error);\n }\n resolve(res ?? { error: \"INTERNAL_ERROR\" });\n },\n );\n });\n\n return {\n messages,\n onlineCount,\n sendMessage,\n };\n}\n","\"use client\";\n\nimport React, { useLayoutEffect, useRef, useState } from \"react\";\nimport { X, Send } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport { useChat, ChatMessage } from \"@/hooks/useChat\";\nimport { format } from \"date-fns\";\n\nexport interface LiveChatTheme {\n primaryColor?: string;\n background?: string;\n textColor?: string;\n buttonSend: {\n color: string;\n } \n}\n\nexport interface LiveChatClassNames {\n container?: string;\n header?: string;\n messages?: string;\n input?: string;\n button?: string;\n}\n\nexport interface LiveChatProps {\n isOpen: boolean;\n onClose: () => void;\n\n // ui\n title?: string;\n\n // customization\n theme?: LiveChatTheme;\n classNames?: LiveChatClassNames;\n\n // slots\n renderMessage?: (message: ChatMessage) => React.ReactNode;\n renderHeader?: () => React.ReactNode;\n authority: string | null;\n customerAuthority: string;\n onConnectWallet?: () => void;\n fallbackMessages?: {\n connectWallet: string;\n description: string;\n };\n}\n\nconst CHAT_MAX_MESSAGE_LENGTH = 200;\nconst AUTO_SCROLL_THRESHOLD_PX = 80;\n\ninterface MessagesSnapshot {\n firstMessageKey: string | null;\n lastMessageKey: string | null;\n messageCount: number;\n scrollHeight: number;\n}\n\nfunction getMessageIdentity(message?: ChatMessage) {\n if (!message) return null;\n\n return [\n message.timestamp,\n message.authority ?? \"anonymous\",\n message.name,\n message.message,\n ].join(\":\");\n}\n\nfunction isNearBottom(element: HTMLDivElement, threshold = AUTO_SCROLL_THRESHOLD_PX) {\n const distanceToBottom =\n element.scrollHeight - element.scrollTop - element.clientHeight;\n\n return distanceToBottom <= threshold;\n}\n\nfunction formatPredictionsCount(count: number) {\n return `${count} ${count === 1 ? \"previsao\" : \"previsoes\"}`;\n}\n\nexport function LiveChat({\n isOpen,\n onClose,\n title = \"Live Chat\",\n theme,\n classNames,\n renderMessage,\n renderHeader,\n authority,\n customerAuthority,\n onConnectWallet,\n}: LiveChatProps) {\n const [inputValue, setInputValue] = useState(\"\");\n const messagesContainerRef = useRef<HTMLDivElement>(null);\n const hasInitialScrollRef = useRef(false);\n const shouldAutoScrollRef = useRef(true);\n const previousSnapshotRef = useRef<MessagesSnapshot | null>(null);\n const primary = theme?.primaryColor || \"#FF3D00\";\n const background = theme?.background || \"var(--chat-bg)\";\n\n const { sendMessage, onlineCount, messages } = useChat({\n authority: authority || undefined,\n customerAuthority: customerAuthority,\n });\n\n const createMessagesSnapshot = (): MessagesSnapshot => {\n const container = messagesContainerRef.current;\n\n return {\n firstMessageKey: getMessageIdentity(messages[0]),\n lastMessageKey: getMessageIdentity(messages[messages.length - 1]),\n messageCount: messages.length,\n scrollHeight: container?.scrollHeight ?? 0,\n };\n };\n\n const scrollToBottom = (behavior: ScrollBehavior = \"auto\") => {\n const container = messagesContainerRef.current;\n if (!container) return;\n\n container.scrollTo({\n top: container.scrollHeight,\n behavior,\n });\n\n shouldAutoScrollRef.current = true;\n };\n\n const preserveScrollPositionAfterPrepend = (\n previousSnapshot: MessagesSnapshot,\n ) => {\n const container = messagesContainerRef.current;\n if (!container) return;\n\n const scrollHeightDelta = container.scrollHeight - previousSnapshot.scrollHeight;\n\n if (scrollHeightDelta !== 0) {\n container.scrollTop += scrollHeightDelta;\n }\n };\n\n const handleMessagesScroll = () => {\n const container = messagesContainerRef.current;\n if (!container) return;\n\n shouldAutoScrollRef.current = isNearBottom(container);\n };\n\n useLayoutEffect(() => {\n if (!isOpen) {\n hasInitialScrollRef.current = false;\n shouldAutoScrollRef.current = true;\n previousSnapshotRef.current = null;\n return;\n }\n\n const container = messagesContainerRef.current;\n if (!container) return;\n\n const nextSnapshot = createMessagesSnapshot();\n const previousSnapshot = previousSnapshotRef.current;\n\n if (!hasInitialScrollRef.current) {\n if (messages.length === 0) {\n previousSnapshotRef.current = nextSnapshot;\n return;\n }\n\n scrollToBottom(\"auto\");\n hasInitialScrollRef.current = true;\n previousSnapshotRef.current = createMessagesSnapshot();\n return;\n }\n\n if (!previousSnapshot) {\n previousSnapshotRef.current = nextSnapshot;\n return;\n }\n\n const hasPrependedMessages =\n nextSnapshot.messageCount > previousSnapshot.messageCount &&\n previousSnapshot.firstMessageKey !== null &&\n nextSnapshot.firstMessageKey !== previousSnapshot.firstMessageKey &&\n nextSnapshot.lastMessageKey === previousSnapshot.lastMessageKey;\n\n const hasAppendedMessages =\n nextSnapshot.messageCount > previousSnapshot.messageCount &&\n previousSnapshot.lastMessageKey !== null &&\n nextSnapshot.lastMessageKey !== previousSnapshot.lastMessageKey &&\n nextSnapshot.firstMessageKey === previousSnapshot.firstMessageKey;\n\n const hasReplacedHistory =\n nextSnapshot.messageCount > 0 &&\n previousSnapshot.messageCount > 0 &&\n nextSnapshot.firstMessageKey !== previousSnapshot.firstMessageKey &&\n nextSnapshot.lastMessageKey !== previousSnapshot.lastMessageKey;\n\n if (hasPrependedMessages) {\n preserveScrollPositionAfterPrepend(previousSnapshot);\n } else if (hasAppendedMessages) {\n if (shouldAutoScrollRef.current) {\n scrollToBottom(\"auto\");\n }\n } else if (hasReplacedHistory && shouldAutoScrollRef.current) {\n scrollToBottom(\"auto\");\n }\n\n previousSnapshotRef.current = createMessagesSnapshot();\n }, [isOpen, messages]);\n\n const handleSendMessage = () => {\n const message = inputValue.trim();\n if (!message) return;\n\n if (!authority) {\n onConnectWallet?.();\n return;\n }\n\n setInputValue(\"\");\n sendMessage(message);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSendMessage();\n }\n };\n\n if (!isOpen) return null;\n\n return (\n <>\n <style>{`\n :root {\n --chat-bg: #ffffff;\n }\n .dark {\n --chat-bg: #15181D;\n }\n `}</style>\n\n <div\n className=\"fixed inset-0 z-[110]\"\n onClick={onClose}\n aria-hidden=\"true\"\n >\n <div\n onClick={(e) => e.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n className={cn(\n \"fixed inset-0 flex flex-col overflow-hidden bg-white dark:bg-app-dark-400 lg:inset-auto lg:right-6 lg:top-[75px] lg:max-w-[340px] lg:max-h-[calc(100vh-90px)] lg:border lg:border-black/10 lg:shadow-2xl lg:dark:border-white/10\",\n \"lg:h-[800px]\",\n classNames?.container,\n )}\n style={{ background }}\n >\n {renderHeader ? (\n renderHeader()\n ) : (\n <div\n className={cn(\n \"flex items-center justify-between border-b border-black/10 px-4 py-4 dark:border-white/10 lg:px-6\",\n classNames?.header,\n )}\n >\n <div className=\"flex items-center gap-3\">\n <h2 className=\"text-lg font-semibold text-triad-dark-100 dark:text-white\">\n {title}\n </h2>\n <div className=\"flex items-center gap-1.5\">\n <div className=\"size-2 rounded-full bg-green-500 animate-pulse\" />\n <span className=\"text-xs text-app-gray-400 dark:text-white\">\n {onlineCount} online\n </span>\n </div>\n </div>\n <button onClick={onClose}>\n <X className=\"text-black dark:text-white\" size={18} />\n </button>\n </div>\n )}\n\n <div\n ref={messagesContainerRef}\n onScroll={handleMessagesScroll}\n className={cn(\n \"flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6\",\n classNames?.messages,\n )}\n style={{ overflowAnchor: \"none\" }}\n >\n {(() => {\n const messageOccurrences = new Map<string, number>();\n\n return messages.map((message) => {\n const baseKey = getMessageIdentity(message) ?? \"message\";\n const occurrence = messageOccurrences.get(baseKey) ?? 0;\n const messageKey = `${baseKey}:${occurrence}`;\n\n messageOccurrences.set(baseKey, occurrence + 1);\n\n return renderMessage ? (\n <React.Fragment key={messageKey}>\n {renderMessage(message)}\n </React.Fragment>\n ) : (\n <div\n key={messageKey}\n className={`flex flex-col gap-1 ${\n message.authority === authority ? \"items-end\" : \"items-start\"\n }`}\n >\n {message.authority !== authority && (\n <span className=\"max-w-[160px] truncate text-xs font-medium text-triad-dark-100 dark:text-white\">\n {message.name}\n </span>\n )}\n\n <div\n className={cn(\n \"max-w-[75%] rounded-2xl px-4 py-2.5\",\n message.authority === authority\n ? \"rounded-br-sm text-white\"\n : \"rounded-bl-sm bg-black/5 text-triad-dark-100 dark:bg-white/5 dark:text-white\",\n )}\n style={\n message.authority === authority\n ? { backgroundColor: primary }\n : undefined\n }\n >\n <p className=\"text-sm break-words\">{message.message}</p>\n </div>\n\n <div className=\"flex items-center gap-2 text-[10px] text-black opacity-50 dark:text-white\">\n <span>{formatPredictionsCount(message.predictionsCount)}</span>\n <span>{format(message.timestamp, \"HH:mm\")}</span>\n </div>\n </div>\n );\n });\n })()}\n </div>\n\n <div className=\"border-t border-black/10 px-4 py-4 dark:border-white/10 lg:px-6\">\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-center gap-2\">\n <input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n maxLength={CHAT_MAX_MESSAGE_LENGTH}\n placeholder=\"Digite sua mensagem...\"\n className={cn(\n \"flex-1 rounded-xl bg-black/5 px-4 py-2.5 text-sm text-app-gray-400 outline-none dark:bg-white/5 dark:text-white\",\n classNames?.input,\n )}\n />\n\n <button\n onClick={handleSendMessage}\n disabled={!inputValue.trim() || (!authority && !onConnectWallet)}\n className={cn(\n \"flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400\",\n classNames?.button,\n )}\n style={{\n opacity: inputValue.trim() ? 1 : 0.5,\n }}\n >\n <Send color={theme?.buttonSend.color} size={18} />\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import {jsx,jsxs,Fragment}from'react/jsx-runtime';import z,{useState,useRef,useEffect}from'react';import {X,Send}from'lucide-react';import {clsx}from'clsx';import {twMerge}from'tailwind-merge';import T from'socket.io-client';import {format}from'date-fns';var W=({label:r,variant:h="primary",className:x="",...c})=>jsx("button",{className:`px-4 py-2 rounded-lg font-medium transition-colors ${{primary:"bg-blue-600 text-white hover:bg-blue-700",secondary:"bg-gray-200 text-gray-800 hover:bg-gray-300"}[h]} ${x}`,...c,children:r});function p(...r){return twMerge(clsx(r))}var D=T("https://beta.triadfi.co",{path:"/socket.io",reconnection:true,reconnectionAttempts:5,reconnectionDelay:1e3,randomizationFactor:.5,timeout:5e3,transports:["websocket"]}),a=D;function w({authority:r,customerAuthority:h}){let[x,c]=useState([]),[l,b]=useState(0),n=h?.trim();return useEffect(()=>{if(!n)return;let u=s=>{c(s);},m=s=>{c(f=>[...f,s]);},o=s=>{b(s.count);},g=()=>{let s=r?{authority:r,customerAuthority:n}:{customerAuthority:n};a.emit("chat:join",s,f=>{f?.error&&console.error("join error",f.error);});};return a.on("chat:history",u),a.on("chat:message",m),a.on("chat:users",o),a.on("connect",g),g(),()=>{a.off("chat:history",u),a.off("chat:message",m),a.off("chat:users",o),a.off("connect",g);}},[r,n]),{messages:x,onlineCount:l,sendMessage:u=>new Promise(m=>{if(!r||!n){m({error:"INVALID_REQUEST"});return}a.emit("chat:message",{authority:r,customerAuthority:n,message:u},o=>{o?.error&&console.error("send error",o.error),m(o??{error:"INTERNAL_ERROR"});});})}}var K=200;function ie({isOpen:r,onClose:h,title:x="Live Chat",theme:c,classNames:l,renderMessage:b,renderHeader:n,authority:d,customerAuthority:u,onConnectWallet:m}){let[o,g]=useState(""),s=useRef(null),f=c?.primaryColor||"#FF3D00",N=c?.background||"var(--chat-bg)",{sendMessage:R,onlineCount:M,messages:y}=w({authority:d||void 0,customerAuthority:u}),L=()=>{s.current?.scrollIntoView({behavior:"smooth"});};useEffect(()=>{L();},[y]);let v=()=>{let e=o.trim();if(e){if(!d){m?.();return}g(""),R(e);}},A=e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),v());};return r?jsxs(Fragment,{children:[jsx("style",{children:`
1
+ import {jsx,jsxs,Fragment}from'react/jsx-runtime';import X$1,{useState,useRef,useLayoutEffect,useEffect}from'react';import {X,Send}from'lucide-react';import {clsx}from'clsx';import {twMerge}from'tailwind-merge';import U from'socket.io-client';import {format}from'date-fns';var ne=({label:t,variant:u="primary",className:h="",...g})=>jsx("button",{className:`px-4 py-2 rounded-lg font-medium transition-colors ${{primary:"bg-blue-600 text-white hover:bg-blue-700",secondary:"bg-gray-200 text-gray-800 hover:bg-gray-300"}[u]} ${h}`,...g,children:t});function y(...t){return twMerge(clsx(t))}var V=U("https://beta.triadfi.co",{path:"/socket.io",reconnection:true,reconnectionAttempts:5,reconnectionDelay:1e3,randomizationFactor:.5,timeout:5e3,transports:["websocket"]}),i=V;function T({authority:t,customerAuthority:u}){let[h,g]=useState([]),[d,M]=useState(0),c=u?.trim();return useEffect(()=>{if(!c)return;let b=o=>{g(o);},p=o=>{g(f=>[...f,o]);},a=o=>{M(o.count);},v=()=>{let o=t?{authority:t,customerAuthority:c}:{customerAuthority:c};i.emit("chat:join",o,f=>{f?.error&&console.error("join error",f.error);});};return i.on("chat:history",b),i.on("chat:message",p),i.on("chat:users",a),i.on("connect",v),v(),()=>{i.off("chat:history",b),i.off("chat:message",p),i.off("chat:users",a),i.off("connect",v);}},[t,c]),{messages:h,onlineCount:d,sendMessage:b=>new Promise(p=>{if(!t||!c){p({error:"INVALID_REQUEST"});return}i.emit("chat:message",{authority:t,customerAuthority:c,message:b},a=>{a?.error&&console.error("send error",a.error),p(a??{error:"INTERNAL_ERROR"});});})}}var Z=200,ee=80;function H(t){return t?[t.timestamp,t.authority??"anonymous",t.name,t.message].join(":"):null}function te(t,u=ee){return t.scrollHeight-t.scrollTop-t.clientHeight<=u}function re(t){return `${t} ${t===1?"previsao":"previsoes"}`}function Ce({isOpen:t,onClose:u,title:h="Live Chat",theme:g,classNames:d,renderMessage:M,renderHeader:c,authority:m,customerAuthority:b,onConnectWallet:p}){let[a,v]=useState(""),o=useRef(null),f=useRef(false),k=useRef(true),C=useRef(null),E=g?.primaryColor||"#FF3D00",P=g?.background||"var(--chat-bg)",{sendMessage:D,onlineCount:B,messages:x}=T({authority:m||void 0,customerAuthority:b}),S=()=>{let r=o.current;return {firstMessageKey:H(x[0]),lastMessageKey:H(x[x.length-1]),messageCount:x.length,scrollHeight:r?.scrollHeight??0}},K=(r="auto")=>{let e=o.current;e&&(e.scrollTo({top:e.scrollHeight,behavior:r}),k.current=true);},_=r=>{let e=o.current;if(!e)return;let s=e.scrollHeight-r.scrollHeight;s!==0&&(e.scrollTop+=s);},$=()=>{let r=o.current;r&&(k.current=te(r));};useLayoutEffect(()=>{if(!t){f.current=false,k.current=true,C.current=null;return}if(!o.current)return;let e=S(),s=C.current;if(!f.current){if(x.length===0){C.current=e;return}K("auto"),f.current=true,C.current=S();return}if(!s){C.current=e;return}let w=e.messageCount>s.messageCount&&s.firstMessageKey!==null&&e.firstMessageKey!==s.firstMessageKey&&e.lastMessageKey===s.lastMessageKey,N=e.messageCount>s.messageCount&&s.lastMessageKey!==null&&e.lastMessageKey!==s.lastMessageKey&&e.firstMessageKey===s.firstMessageKey,F=e.messageCount>0&&s.messageCount>0&&e.firstMessageKey!==s.firstMessageKey&&e.lastMessageKey!==s.lastMessageKey;w?_(s):N?k.current&&K("auto"):F&&k.current&&K("auto"),C.current=S();},[t,x]);let L=()=>{let r=a.trim();if(r){if(!m){p?.();return}v(""),D(r);}},z=r=>{r.key==="Enter"&&!r.shiftKey&&(r.preventDefault(),L());};return t?jsxs(Fragment,{children:[jsx("style",{children:`
2
2
  :root {
3
3
  --chat-bg: #ffffff;
4
4
  }
5
5
  .dark {
6
6
  --chat-bg: #15181D;
7
7
  }
8
- `}),jsxs("div",{onClick:e=>e.stopPropagation(),className:p("fixed inset-0 z-[110] flex flex-col overflow-hidden bg-white dark:bg-app-dark-400 lg:inset-auto lg:right-6 lg:top-[75px] lg:max-w-[340px] lg:max-h-[calc(100vh-90px)] lg:border lg:border-black/10 lg:shadow-2xl lg:dark:border-white/10","lg:h-[800px]",l?.container),style:{background:N},children:[n?n():jsxs("div",{className:p("flex items-center justify-between border-b border-black/10 px-4 py-4 dark:border-white/10 lg:px-6",l?.header),children:[jsxs("div",{className:"flex items-center gap-3",children:[jsx("h2",{className:"text-lg font-semibold text-triad-dark-100 dark:text-white",children:x}),jsxs("div",{className:"flex items-center gap-1.5",children:[jsx("div",{className:"size-2 rounded-full bg-green-500 animate-pulse"}),jsxs("span",{className:"text-xs text-app-gray-400 dark:text-white",children:[M," online"]})]})]}),jsx("button",{onClick:h,children:jsx(X,{className:"text-black dark:text-white",size:18})})]}),jsxs("div",{className:p("flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6",l?.messages),children:[y.map((e,C)=>b?jsx(z.Fragment,{children:b(e)},C):jsxs("div",{className:`flex flex-col gap-1 ${e.authority===d?"items-end":"items-start"}`,children:[e.authority!==d&&jsx("span",{className:"max-w-[160px] truncate text-xs font-medium text-triad-dark-100 dark:text-white",children:e.name}),jsx("div",{className:p("max-w-[75%] rounded-2xl px-4 py-2.5",e.authority===d?"rounded-br-sm text-white":"rounded-bl-sm bg-black/5 text-triad-dark-100 dark:bg-white/5 dark:text-white"),style:e.authority===d?{backgroundColor:f}:void 0,children:jsx("p",{className:"text-sm break-words",children:e.message})}),jsx("span",{className:"text-[10px] text-black opacity-50 dark:text-white",children:format(e.timestamp,"HH:mm")})]},C)),jsx("div",{ref:s})]}),jsx("div",{className:"border-t border-black/10 px-4 py-4 dark:border-white/10 lg:px-6",children:jsx("div",{className:"flex flex-col gap-2",children:jsxs("div",{className:"flex items-center gap-2",children:[jsx("input",{value:o,onChange:e=>g(e.target.value),onKeyDown:A,maxLength:K,placeholder:"Digite sua mensagem...",className:p("flex-1 rounded-xl bg-black/5 px-4 py-2.5 text-sm text-app-gray-400 outline-none dark:bg-white/5 dark:text-white",l?.input)}),jsx("button",{onClick:v,disabled:!o.trim()||!d&&!m,className:p("flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400",l?.button),style:{opacity:o.trim()?1:.5},children:jsx(Send,{color:c?.buttonSend.color,size:18})})]})})})]})]}):null}export{W as ButtonWidget,ie as LiveChat};//# sourceMappingURL=index.mjs.map
8
+ `}),jsx("div",{className:"fixed inset-0 z-[110]",onClick:u,"aria-hidden":"true",children:jsxs("div",{onClick:r=>r.stopPropagation(),role:"dialog","aria-modal":"true","aria-label":h,className:y("fixed inset-0 flex flex-col overflow-hidden bg-white dark:bg-app-dark-400 lg:inset-auto lg:right-6 lg:top-[75px] lg:max-w-[340px] lg:max-h-[calc(100vh-90px)] lg:border lg:border-black/10 lg:shadow-2xl lg:dark:border-white/10","lg:h-[800px]",d?.container),style:{background:P},children:[c?c():jsxs("div",{className:y("flex items-center justify-between border-b border-black/10 px-4 py-4 dark:border-white/10 lg:px-6",d?.header),children:[jsxs("div",{className:"flex items-center gap-3",children:[jsx("h2",{className:"text-lg font-semibold text-triad-dark-100 dark:text-white",children:h}),jsxs("div",{className:"flex items-center gap-1.5",children:[jsx("div",{className:"size-2 rounded-full bg-green-500 animate-pulse"}),jsxs("span",{className:"text-xs text-app-gray-400 dark:text-white",children:[B," online"]})]})]}),jsx("button",{onClick:u,children:jsx(X,{className:"text-black dark:text-white",size:18})})]}),jsx("div",{ref:o,onScroll:$,className:y("flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6",d?.messages),style:{overflowAnchor:"none"},children:(()=>{let r=new Map;return x.map(e=>{let s=H(e)??"message",w=r.get(s)??0,N=`${s}:${w}`;return r.set(s,w+1),M?jsx(X$1.Fragment,{children:M(e)},N):jsxs("div",{className:`flex flex-col gap-1 ${e.authority===m?"items-end":"items-start"}`,children:[e.authority!==m&&jsx("span",{className:"max-w-[160px] truncate text-xs font-medium text-triad-dark-100 dark:text-white",children:e.name}),jsx("div",{className:y("max-w-[75%] rounded-2xl px-4 py-2.5",e.authority===m?"rounded-br-sm text-white":"rounded-bl-sm bg-black/5 text-triad-dark-100 dark:bg-white/5 dark:text-white"),style:e.authority===m?{backgroundColor:E}:void 0,children:jsx("p",{className:"text-sm break-words",children:e.message})}),jsxs("div",{className:"flex items-center gap-2 text-[10px] text-black opacity-50 dark:text-white",children:[jsx("span",{children:re(e.predictionsCount)}),jsx("span",{children:format(e.timestamp,"HH:mm")})]})]},N)})})()}),jsx("div",{className:"border-t border-black/10 px-4 py-4 dark:border-white/10 lg:px-6",children:jsx("div",{className:"flex flex-col gap-2",children:jsxs("div",{className:"flex items-center gap-2",children:[jsx("input",{value:a,onChange:r=>v(r.target.value),onKeyDown:z,maxLength:Z,placeholder:"Digite sua mensagem...",className:y("flex-1 rounded-xl bg-black/5 px-4 py-2.5 text-sm text-app-gray-400 outline-none dark:bg-white/5 dark:text-white",d?.input)}),jsx("button",{onClick:L,disabled:!a.trim()||!m&&!p,className:y("flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400",d?.button),style:{opacity:a.trim()?1:.5},children:jsx(Send,{color:g?.buttonSend.color,size:18})})]})})})]})})]}):null}export{ne as ButtonWidget,Ce as LiveChat};//# sourceMappingURL=index.mjs.map
9
9
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/ButtonWidget.tsx","../src/utils/cn.ts","../src/utils/socket.ts","../src/hooks/useChat.ts","../src/components/LiveChat.tsx"],"names":["ButtonWidget","label","variant","className","props","jsx","cn","classes","twMerge","clsx","socket","io","socket_default","useChat","authority","customerAuthority","messages","setMessages","useState","onlineCount","setOnlineCount","normalizedCustomerAuthority","useEffect","handleHistory","msgs","handleMessage","msg","prev","handleUsers","data","joinChat","joinPayload","res","message","resolve","CHAT_MAX_MESSAGE_LENGTH","LiveChat","isOpen","onClose","title","theme","classNames","renderMessage","renderHeader","onConnectWallet","inputValue","setInputValue","messagesEndRef","useRef","primary","background","sendMessage","scrollToBottom","handleSendMessage","handleKeyDown","jsxs","Fragment","X","idx","React","format","Send"],"mappings":"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","file":"index.mjs","sourcesContent":["'use client';\n\nimport React from 'react';\n\nexport interface ButtonWidgetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n variant?: 'primary' | 'secondary';\n}\n\nexport const ButtonWidget: React.FC<ButtonWidgetProps> = ({ \n label, \n variant = 'primary', \n className = '',\n ...props \n}) => {\n const baseStyles = 'px-4 py-2 rounded-lg font-medium transition-colors';\n const variants = {\n primary: 'bg-blue-600 text-white hover:bg-blue-700',\n secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300'\n };\n\n return (\n <button \n className={`${baseStyles} ${variants[variant]} ${className}`}\n {...props}\n >\n {label}\n </button>\n );\n};\n","import type { ClassValue } from \"clsx\";\nimport { clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...classes: ClassValue[]) {\n return twMerge(clsx(classes));\n}\n","import io from \"socket.io-client\";\n\nconst socket = io(\"https://beta.triadfi.co\", {\n path: \"/socket.io\",\n reconnection: true,\n reconnectionAttempts: 5,\n reconnectionDelay: 1000,\n randomizationFactor: 0.5,\n timeout: 5000,\n transports: [\"websocket\"],\n});\n\nexport default socket;\n","import socket from \"@/utils/socket\";\nimport { useEffect, useState } from \"react\";\n\nexport interface ChatMessage {\n authority?: string;\n name: string;\n image: string;\n predictionsCount: number;\n message: string;\n timestamp: number;\n}\n\ninterface ChatUsersPayload {\n count: number;\n}\n\ninterface ChatResponse {\n error: string | null;\n}\n\nexport function useChat({\n authority,\n customerAuthority,\n}: {\n authority?: string;\n customerAuthority: string;\n}) {\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [onlineCount, setOnlineCount] = useState(0);\n const normalizedCustomerAuthority = customerAuthority?.trim();\n\n useEffect(() => {\n if (!normalizedCustomerAuthority) return;\n\n const handleHistory = (msgs: ChatMessage[]) => {\n setMessages(msgs);\n };\n\n const handleMessage = (msg: ChatMessage) => {\n setMessages((prev) => [...prev, msg]);\n };\n\n const handleUsers = (data: ChatUsersPayload) => {\n setOnlineCount(data.count);\n };\n\n const joinChat = () => {\n const joinPayload = authority\n ? { authority, customerAuthority: normalizedCustomerAuthority }\n : { customerAuthority: normalizedCustomerAuthority };\n\n socket.emit(\"chat:join\", joinPayload, (res: ChatResponse) => {\n if (res?.error) {\n console.error(\"join error\", res.error);\n }\n });\n };\n\n socket.on(\"chat:history\", handleHistory);\n socket.on(\"chat:message\", handleMessage);\n socket.on(\"chat:users\", handleUsers);\n socket.on(\"connect\", joinChat);\n joinChat();\n\n return () => {\n socket.off(\"chat:history\", handleHistory);\n socket.off(\"chat:message\", handleMessage);\n socket.off(\"chat:users\", handleUsers);\n socket.off(\"connect\", joinChat);\n };\n }, [authority, normalizedCustomerAuthority]);\n\n const sendMessage = (message: string): Promise<ChatResponse> =>\n new Promise((resolve) => {\n if (!authority || !normalizedCustomerAuthority) {\n resolve({ error: \"INVALID_REQUEST\" });\n return;\n }\n\n socket.emit(\n \"chat:message\",\n { authority, customerAuthority: normalizedCustomerAuthority, message },\n (res: ChatResponse) => {\n if (res?.error) {\n console.error(\"send error\", res.error);\n }\n resolve(res ?? { error: \"INTERNAL_ERROR\" });\n },\n );\n });\n\n return {\n messages,\n onlineCount,\n sendMessage,\n };\n}\n","\"use client\";\n\nimport React, { useState, useRef, useEffect } from \"react\";\nimport { X, Send } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport { useChat, ChatMessage } from \"@/hooks/useChat\";\nimport { format } from \"date-fns\";\n\nexport interface LiveChatTheme {\n primaryColor?: string;\n background?: string;\n textColor?: string;\n buttonSend: {\n color: string;\n } \n}\n\nexport interface LiveChatClassNames {\n container?: string;\n header?: string;\n messages?: string;\n input?: string;\n button?: string;\n}\n\nexport interface LiveChatProps {\n isOpen: boolean;\n onClose: () => void;\n\n // ui\n title?: string;\n\n // customization\n theme?: LiveChatTheme;\n classNames?: LiveChatClassNames;\n\n // slots\n renderMessage?: (message: ChatMessage) => React.ReactNode;\n renderHeader?: () => React.ReactNode;\n authority: string | null;\n customerAuthority: string;\n onConnectWallet?: () => void;\n fallbackMessages?: {\n connectWallet: string;\n description: string;\n };\n}\n\nconst CHAT_MAX_MESSAGE_LENGTH = 200;\n\nexport function LiveChat({\n isOpen,\n onClose,\n title = \"Live Chat\",\n theme,\n classNames,\n renderMessage,\n renderHeader,\n authority,\n customerAuthority,\n onConnectWallet,\n}: LiveChatProps) {\n const [inputValue, setInputValue] = useState(\"\");\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const primary = theme?.primaryColor || \"#FF3D00\";\n const background = theme?.background || \"var(--chat-bg)\";\n\n const { sendMessage, onlineCount, messages } = useChat({\n authority: authority || undefined,\n customerAuthority: customerAuthority,\n });\n\n const scrollToBottom = () => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n };\n\n useEffect(() => {\n scrollToBottom();\n }, [messages]);\n\n const handleSendMessage = () => {\n const message = inputValue.trim();\n if (!message) return;\n\n if (!authority) {\n onConnectWallet?.();\n return;\n }\n\n setInputValue(\"\");\n sendMessage(message);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSendMessage();\n }\n };\n\n if (!isOpen) return null;\n\n return (\n <>\n <style>{`\n :root {\n --chat-bg: #ffffff;\n }\n .dark {\n --chat-bg: #15181D;\n }\n `}</style>\n\n <div\n onClick={(e) => e.stopPropagation()}\n className={cn(\n \"fixed inset-0 z-[110] flex flex-col overflow-hidden bg-white dark:bg-app-dark-400 lg:inset-auto lg:right-6 lg:top-[75px] lg:max-w-[340px] lg:max-h-[calc(100vh-90px)] lg:border lg:border-black/10 lg:shadow-2xl lg:dark:border-white/10\",\n \"lg:h-[800px]\",\n classNames?.container,\n )}\n style={{ background }}\n >\n {renderHeader ? (\n renderHeader()\n ) : (\n <div\n className={cn(\n \"flex items-center justify-between border-b border-black/10 px-4 py-4 dark:border-white/10 lg:px-6\",\n classNames?.header,\n )}\n >\n <div className=\"flex items-center gap-3\">\n <h2 className=\"text-lg font-semibold text-triad-dark-100 dark:text-white\">\n {title}\n </h2>\n <div className=\"flex items-center gap-1.5\">\n <div className=\"size-2 rounded-full bg-green-500 animate-pulse\" />\n <span className=\"text-xs text-app-gray-400 dark:text-white\">\n {onlineCount} online\n </span>\n </div>\n </div>\n <button onClick={onClose}>\n <X className=\"text-black dark:text-white\" size={18} />\n </button>\n </div>\n )}\n\n <div\n className={cn(\n \"flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6\",\n classNames?.messages,\n )}\n >\n {messages.map((message, idx) =>\n renderMessage ? (\n <React.Fragment key={idx}>{renderMessage(message)}</React.Fragment>\n ) : (\n <div\n key={idx}\n className={`flex flex-col gap-1 ${\n message.authority === authority ? \"items-end\" : \"items-start\"\n }`}\n >\n {message.authority !== authority && (\n <span className=\"max-w-[160px] truncate text-xs font-medium text-triad-dark-100 dark:text-white\">\n {message.name}\n </span>\n )}\n\n <div\n className={cn(\n \"max-w-[75%] rounded-2xl px-4 py-2.5\",\n message.authority === authority\n ? \"rounded-br-sm text-white\"\n : \"rounded-bl-sm bg-black/5 text-triad-dark-100 dark:bg-white/5 dark:text-white\",\n )}\n style={\n message.authority === authority\n ? { backgroundColor: primary }\n : undefined\n }\n >\n <p className=\"text-sm break-words\">{message.message}</p>\n </div>\n\n <span className=\"text-[10px] text-black opacity-50 dark:text-white\">\n {format(message.timestamp, \"HH:mm\")}\n </span>\n </div>\n ),\n )}\n <div ref={messagesEndRef} />\n </div>\n\n <div className=\"border-t border-black/10 px-4 py-4 dark:border-white/10 lg:px-6\">\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-center gap-2\">\n <input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n maxLength={CHAT_MAX_MESSAGE_LENGTH}\n placeholder=\"Digite sua mensagem...\"\n className={cn(\n \"flex-1 rounded-xl bg-black/5 px-4 py-2.5 text-sm text-app-gray-400 outline-none dark:bg-white/5 dark:text-white\",\n classNames?.input,\n )}\n />\n\n <button\n onClick={handleSendMessage}\n disabled={!inputValue.trim() || (!authority && !onConnectWallet)}\n className={cn(\n \"flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400\",\n classNames?.button,\n )}\n style={{\n opacity: inputValue.trim() ? 1 : 0.5,\n }}\n >\n <Send color={theme?.buttonSend.color} size={18} />\n </button>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/components/ButtonWidget.tsx","../src/utils/cn.ts","../src/utils/socket.ts","../src/hooks/useChat.ts","../src/components/LiveChat.tsx"],"names":["ButtonWidget","label","variant","className","props","jsx","cn","classes","twMerge","clsx","socket","io","socket_default","useChat","authority","customerAuthority","messages","setMessages","useState","onlineCount","setOnlineCount","normalizedCustomerAuthority","useEffect","handleHistory","msgs","handleMessage","msg","prev","handleUsers","data","joinChat","joinPayload","res","message","resolve","CHAT_MAX_MESSAGE_LENGTH","AUTO_SCROLL_THRESHOLD_PX","getMessageIdentity","isNearBottom","element","threshold","formatPredictionsCount","count","LiveChat","isOpen","onClose","title","theme","classNames","renderMessage","renderHeader","onConnectWallet","inputValue","setInputValue","messagesContainerRef","useRef","hasInitialScrollRef","shouldAutoScrollRef","previousSnapshotRef","primary","background","sendMessage","createMessagesSnapshot","container","scrollToBottom","behavior","preserveScrollPositionAfterPrepend","previousSnapshot","scrollHeightDelta","handleMessagesScroll","useLayoutEffect","nextSnapshot","hasPrependedMessages","hasAppendedMessages","hasReplacedHistory","handleSendMessage","handleKeyDown","e","jsxs","Fragment","X","messageOccurrences","baseKey","occurrence","messageKey","React","format","Send"],"mappings":"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","file":"index.mjs","sourcesContent":["'use client';\n\nimport React from 'react';\n\nexport interface ButtonWidgetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n variant?: 'primary' | 'secondary';\n}\n\nexport const ButtonWidget: React.FC<ButtonWidgetProps> = ({ \n label, \n variant = 'primary', \n className = '',\n ...props \n}) => {\n const baseStyles = 'px-4 py-2 rounded-lg font-medium transition-colors';\n const variants = {\n primary: 'bg-blue-600 text-white hover:bg-blue-700',\n secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300'\n };\n\n return (\n <button \n className={`${baseStyles} ${variants[variant]} ${className}`}\n {...props}\n >\n {label}\n </button>\n );\n};\n","import type { ClassValue } from \"clsx\";\nimport { clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...classes: ClassValue[]) {\n return twMerge(clsx(classes));\n}\n","import io from \"socket.io-client\";\n\nconst socket = io(\"https://beta.triadfi.co\", {\n path: \"/socket.io\",\n reconnection: true,\n reconnectionAttempts: 5,\n reconnectionDelay: 1000,\n randomizationFactor: 0.5,\n timeout: 5000,\n transports: [\"websocket\"],\n});\n\nexport default socket;\n","import socket from \"@/utils/socket\";\nimport { useEffect, useState } from \"react\";\n\nexport interface ChatMessage {\n authority?: string;\n name: string;\n image: string;\n predictionsCount: number;\n message: string;\n timestamp: number;\n}\n\ninterface ChatUsersPayload {\n count: number;\n}\n\ninterface ChatResponse {\n error: string | null;\n}\n\nexport function useChat({\n authority,\n customerAuthority,\n}: {\n authority?: string;\n customerAuthority: string;\n}) {\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [onlineCount, setOnlineCount] = useState(0);\n const normalizedCustomerAuthority = customerAuthority?.trim();\n\n useEffect(() => {\n if (!normalizedCustomerAuthority) return;\n\n const handleHistory = (msgs: ChatMessage[]) => {\n setMessages(msgs);\n };\n\n const handleMessage = (msg: ChatMessage) => {\n setMessages((prev) => [...prev, msg]);\n };\n\n const handleUsers = (data: ChatUsersPayload) => {\n setOnlineCount(data.count);\n };\n\n const joinChat = () => {\n const joinPayload = authority\n ? { authority, customerAuthority: normalizedCustomerAuthority }\n : { customerAuthority: normalizedCustomerAuthority };\n\n socket.emit(\"chat:join\", joinPayload, (res: ChatResponse) => {\n if (res?.error) {\n console.error(\"join error\", res.error);\n }\n });\n };\n\n socket.on(\"chat:history\", handleHistory);\n socket.on(\"chat:message\", handleMessage);\n socket.on(\"chat:users\", handleUsers);\n socket.on(\"connect\", joinChat);\n joinChat();\n\n return () => {\n socket.off(\"chat:history\", handleHistory);\n socket.off(\"chat:message\", handleMessage);\n socket.off(\"chat:users\", handleUsers);\n socket.off(\"connect\", joinChat);\n };\n }, [authority, normalizedCustomerAuthority]);\n\n const sendMessage = (message: string): Promise<ChatResponse> =>\n new Promise((resolve) => {\n if (!authority || !normalizedCustomerAuthority) {\n resolve({ error: \"INVALID_REQUEST\" });\n return;\n }\n\n socket.emit(\n \"chat:message\",\n { authority, customerAuthority: normalizedCustomerAuthority, message },\n (res: ChatResponse) => {\n if (res?.error) {\n console.error(\"send error\", res.error);\n }\n resolve(res ?? { error: \"INTERNAL_ERROR\" });\n },\n );\n });\n\n return {\n messages,\n onlineCount,\n sendMessage,\n };\n}\n","\"use client\";\n\nimport React, { useLayoutEffect, useRef, useState } from \"react\";\nimport { X, Send } from \"lucide-react\";\nimport { cn } from \"@/utils/cn\";\nimport { useChat, ChatMessage } from \"@/hooks/useChat\";\nimport { format } from \"date-fns\";\n\nexport interface LiveChatTheme {\n primaryColor?: string;\n background?: string;\n textColor?: string;\n buttonSend: {\n color: string;\n } \n}\n\nexport interface LiveChatClassNames {\n container?: string;\n header?: string;\n messages?: string;\n input?: string;\n button?: string;\n}\n\nexport interface LiveChatProps {\n isOpen: boolean;\n onClose: () => void;\n\n // ui\n title?: string;\n\n // customization\n theme?: LiveChatTheme;\n classNames?: LiveChatClassNames;\n\n // slots\n renderMessage?: (message: ChatMessage) => React.ReactNode;\n renderHeader?: () => React.ReactNode;\n authority: string | null;\n customerAuthority: string;\n onConnectWallet?: () => void;\n fallbackMessages?: {\n connectWallet: string;\n description: string;\n };\n}\n\nconst CHAT_MAX_MESSAGE_LENGTH = 200;\nconst AUTO_SCROLL_THRESHOLD_PX = 80;\n\ninterface MessagesSnapshot {\n firstMessageKey: string | null;\n lastMessageKey: string | null;\n messageCount: number;\n scrollHeight: number;\n}\n\nfunction getMessageIdentity(message?: ChatMessage) {\n if (!message) return null;\n\n return [\n message.timestamp,\n message.authority ?? \"anonymous\",\n message.name,\n message.message,\n ].join(\":\");\n}\n\nfunction isNearBottom(element: HTMLDivElement, threshold = AUTO_SCROLL_THRESHOLD_PX) {\n const distanceToBottom =\n element.scrollHeight - element.scrollTop - element.clientHeight;\n\n return distanceToBottom <= threshold;\n}\n\nfunction formatPredictionsCount(count: number) {\n return `${count} ${count === 1 ? \"previsao\" : \"previsoes\"}`;\n}\n\nexport function LiveChat({\n isOpen,\n onClose,\n title = \"Live Chat\",\n theme,\n classNames,\n renderMessage,\n renderHeader,\n authority,\n customerAuthority,\n onConnectWallet,\n}: LiveChatProps) {\n const [inputValue, setInputValue] = useState(\"\");\n const messagesContainerRef = useRef<HTMLDivElement>(null);\n const hasInitialScrollRef = useRef(false);\n const shouldAutoScrollRef = useRef(true);\n const previousSnapshotRef = useRef<MessagesSnapshot | null>(null);\n const primary = theme?.primaryColor || \"#FF3D00\";\n const background = theme?.background || \"var(--chat-bg)\";\n\n const { sendMessage, onlineCount, messages } = useChat({\n authority: authority || undefined,\n customerAuthority: customerAuthority,\n });\n\n const createMessagesSnapshot = (): MessagesSnapshot => {\n const container = messagesContainerRef.current;\n\n return {\n firstMessageKey: getMessageIdentity(messages[0]),\n lastMessageKey: getMessageIdentity(messages[messages.length - 1]),\n messageCount: messages.length,\n scrollHeight: container?.scrollHeight ?? 0,\n };\n };\n\n const scrollToBottom = (behavior: ScrollBehavior = \"auto\") => {\n const container = messagesContainerRef.current;\n if (!container) return;\n\n container.scrollTo({\n top: container.scrollHeight,\n behavior,\n });\n\n shouldAutoScrollRef.current = true;\n };\n\n const preserveScrollPositionAfterPrepend = (\n previousSnapshot: MessagesSnapshot,\n ) => {\n const container = messagesContainerRef.current;\n if (!container) return;\n\n const scrollHeightDelta = container.scrollHeight - previousSnapshot.scrollHeight;\n\n if (scrollHeightDelta !== 0) {\n container.scrollTop += scrollHeightDelta;\n }\n };\n\n const handleMessagesScroll = () => {\n const container = messagesContainerRef.current;\n if (!container) return;\n\n shouldAutoScrollRef.current = isNearBottom(container);\n };\n\n useLayoutEffect(() => {\n if (!isOpen) {\n hasInitialScrollRef.current = false;\n shouldAutoScrollRef.current = true;\n previousSnapshotRef.current = null;\n return;\n }\n\n const container = messagesContainerRef.current;\n if (!container) return;\n\n const nextSnapshot = createMessagesSnapshot();\n const previousSnapshot = previousSnapshotRef.current;\n\n if (!hasInitialScrollRef.current) {\n if (messages.length === 0) {\n previousSnapshotRef.current = nextSnapshot;\n return;\n }\n\n scrollToBottom(\"auto\");\n hasInitialScrollRef.current = true;\n previousSnapshotRef.current = createMessagesSnapshot();\n return;\n }\n\n if (!previousSnapshot) {\n previousSnapshotRef.current = nextSnapshot;\n return;\n }\n\n const hasPrependedMessages =\n nextSnapshot.messageCount > previousSnapshot.messageCount &&\n previousSnapshot.firstMessageKey !== null &&\n nextSnapshot.firstMessageKey !== previousSnapshot.firstMessageKey &&\n nextSnapshot.lastMessageKey === previousSnapshot.lastMessageKey;\n\n const hasAppendedMessages =\n nextSnapshot.messageCount > previousSnapshot.messageCount &&\n previousSnapshot.lastMessageKey !== null &&\n nextSnapshot.lastMessageKey !== previousSnapshot.lastMessageKey &&\n nextSnapshot.firstMessageKey === previousSnapshot.firstMessageKey;\n\n const hasReplacedHistory =\n nextSnapshot.messageCount > 0 &&\n previousSnapshot.messageCount > 0 &&\n nextSnapshot.firstMessageKey !== previousSnapshot.firstMessageKey &&\n nextSnapshot.lastMessageKey !== previousSnapshot.lastMessageKey;\n\n if (hasPrependedMessages) {\n preserveScrollPositionAfterPrepend(previousSnapshot);\n } else if (hasAppendedMessages) {\n if (shouldAutoScrollRef.current) {\n scrollToBottom(\"auto\");\n }\n } else if (hasReplacedHistory && shouldAutoScrollRef.current) {\n scrollToBottom(\"auto\");\n }\n\n previousSnapshotRef.current = createMessagesSnapshot();\n }, [isOpen, messages]);\n\n const handleSendMessage = () => {\n const message = inputValue.trim();\n if (!message) return;\n\n if (!authority) {\n onConnectWallet?.();\n return;\n }\n\n setInputValue(\"\");\n sendMessage(message);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSendMessage();\n }\n };\n\n if (!isOpen) return null;\n\n return (\n <>\n <style>{`\n :root {\n --chat-bg: #ffffff;\n }\n .dark {\n --chat-bg: #15181D;\n }\n `}</style>\n\n <div\n className=\"fixed inset-0 z-[110]\"\n onClick={onClose}\n aria-hidden=\"true\"\n >\n <div\n onClick={(e) => e.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n className={cn(\n \"fixed inset-0 flex flex-col overflow-hidden bg-white dark:bg-app-dark-400 lg:inset-auto lg:right-6 lg:top-[75px] lg:max-w-[340px] lg:max-h-[calc(100vh-90px)] lg:border lg:border-black/10 lg:shadow-2xl lg:dark:border-white/10\",\n \"lg:h-[800px]\",\n classNames?.container,\n )}\n style={{ background }}\n >\n {renderHeader ? (\n renderHeader()\n ) : (\n <div\n className={cn(\n \"flex items-center justify-between border-b border-black/10 px-4 py-4 dark:border-white/10 lg:px-6\",\n classNames?.header,\n )}\n >\n <div className=\"flex items-center gap-3\">\n <h2 className=\"text-lg font-semibold text-triad-dark-100 dark:text-white\">\n {title}\n </h2>\n <div className=\"flex items-center gap-1.5\">\n <div className=\"size-2 rounded-full bg-green-500 animate-pulse\" />\n <span className=\"text-xs text-app-gray-400 dark:text-white\">\n {onlineCount} online\n </span>\n </div>\n </div>\n <button onClick={onClose}>\n <X className=\"text-black dark:text-white\" size={18} />\n </button>\n </div>\n )}\n\n <div\n ref={messagesContainerRef}\n onScroll={handleMessagesScroll}\n className={cn(\n \"flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6\",\n classNames?.messages,\n )}\n style={{ overflowAnchor: \"none\" }}\n >\n {(() => {\n const messageOccurrences = new Map<string, number>();\n\n return messages.map((message) => {\n const baseKey = getMessageIdentity(message) ?? \"message\";\n const occurrence = messageOccurrences.get(baseKey) ?? 0;\n const messageKey = `${baseKey}:${occurrence}`;\n\n messageOccurrences.set(baseKey, occurrence + 1);\n\n return renderMessage ? (\n <React.Fragment key={messageKey}>\n {renderMessage(message)}\n </React.Fragment>\n ) : (\n <div\n key={messageKey}\n className={`flex flex-col gap-1 ${\n message.authority === authority ? \"items-end\" : \"items-start\"\n }`}\n >\n {message.authority !== authority && (\n <span className=\"max-w-[160px] truncate text-xs font-medium text-triad-dark-100 dark:text-white\">\n {message.name}\n </span>\n )}\n\n <div\n className={cn(\n \"max-w-[75%] rounded-2xl px-4 py-2.5\",\n message.authority === authority\n ? \"rounded-br-sm text-white\"\n : \"rounded-bl-sm bg-black/5 text-triad-dark-100 dark:bg-white/5 dark:text-white\",\n )}\n style={\n message.authority === authority\n ? { backgroundColor: primary }\n : undefined\n }\n >\n <p className=\"text-sm break-words\">{message.message}</p>\n </div>\n\n <div className=\"flex items-center gap-2 text-[10px] text-black opacity-50 dark:text-white\">\n <span>{formatPredictionsCount(message.predictionsCount)}</span>\n <span>{format(message.timestamp, \"HH:mm\")}</span>\n </div>\n </div>\n );\n });\n })()}\n </div>\n\n <div className=\"border-t border-black/10 px-4 py-4 dark:border-white/10 lg:px-6\">\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-center gap-2\">\n <input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n maxLength={CHAT_MAX_MESSAGE_LENGTH}\n placeholder=\"Digite sua mensagem...\"\n className={cn(\n \"flex-1 rounded-xl bg-black/5 px-4 py-2.5 text-sm text-app-gray-400 outline-none dark:bg-white/5 dark:text-white\",\n classNames?.input,\n )}\n />\n\n <button\n onClick={handleSendMessage}\n disabled={!inputValue.trim() || (!authority && !onConnectWallet)}\n className={cn(\n \"flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400\",\n classNames?.button,\n )}\n style={{\n opacity: inputValue.trim() ? 1 : 0.5,\n }}\n >\n <Send color={theme?.buttonSend.color} size={18} />\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triadxyz/widgets",
3
- "version": "0.0.9",
3
+ "version": "0.1.0",
4
4
  "description": "Shared widgets and logic for Triad Next.js whitelabels",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import React, { useState, useRef, useEffect } from "react";
3
+ import React, { useLayoutEffect, useRef, useState } from "react";
4
4
  import { X, Send } from "lucide-react";
5
5
  import { cn } from "@/utils/cn";
6
6
  import { useChat, ChatMessage } from "@/hooks/useChat";
@@ -47,6 +47,36 @@ export interface LiveChatProps {
47
47
  }
48
48
 
49
49
  const CHAT_MAX_MESSAGE_LENGTH = 200;
50
+ const AUTO_SCROLL_THRESHOLD_PX = 80;
51
+
52
+ interface MessagesSnapshot {
53
+ firstMessageKey: string | null;
54
+ lastMessageKey: string | null;
55
+ messageCount: number;
56
+ scrollHeight: number;
57
+ }
58
+
59
+ function getMessageIdentity(message?: ChatMessage) {
60
+ if (!message) return null;
61
+
62
+ return [
63
+ message.timestamp,
64
+ message.authority ?? "anonymous",
65
+ message.name,
66
+ message.message,
67
+ ].join(":");
68
+ }
69
+
70
+ function isNearBottom(element: HTMLDivElement, threshold = AUTO_SCROLL_THRESHOLD_PX) {
71
+ const distanceToBottom =
72
+ element.scrollHeight - element.scrollTop - element.clientHeight;
73
+
74
+ return distanceToBottom <= threshold;
75
+ }
76
+
77
+ function formatPredictionsCount(count: number) {
78
+ return `${count} ${count === 1 ? "previsao" : "previsoes"}`;
79
+ }
50
80
 
51
81
  export function LiveChat({
52
82
  isOpen,
@@ -61,7 +91,10 @@ export function LiveChat({
61
91
  onConnectWallet,
62
92
  }: LiveChatProps) {
63
93
  const [inputValue, setInputValue] = useState("");
64
- const messagesEndRef = useRef<HTMLDivElement>(null);
94
+ const messagesContainerRef = useRef<HTMLDivElement>(null);
95
+ const hasInitialScrollRef = useRef(false);
96
+ const shouldAutoScrollRef = useRef(true);
97
+ const previousSnapshotRef = useRef<MessagesSnapshot | null>(null);
65
98
  const primary = theme?.primaryColor || "#FF3D00";
66
99
  const background = theme?.background || "var(--chat-bg)";
67
100
 
@@ -70,13 +103,110 @@ export function LiveChat({
70
103
  customerAuthority: customerAuthority,
71
104
  });
72
105
 
73
- const scrollToBottom = () => {
74
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
106
+ const createMessagesSnapshot = (): MessagesSnapshot => {
107
+ const container = messagesContainerRef.current;
108
+
109
+ return {
110
+ firstMessageKey: getMessageIdentity(messages[0]),
111
+ lastMessageKey: getMessageIdentity(messages[messages.length - 1]),
112
+ messageCount: messages.length,
113
+ scrollHeight: container?.scrollHeight ?? 0,
114
+ };
115
+ };
116
+
117
+ const scrollToBottom = (behavior: ScrollBehavior = "auto") => {
118
+ const container = messagesContainerRef.current;
119
+ if (!container) return;
120
+
121
+ container.scrollTo({
122
+ top: container.scrollHeight,
123
+ behavior,
124
+ });
125
+
126
+ shouldAutoScrollRef.current = true;
127
+ };
128
+
129
+ const preserveScrollPositionAfterPrepend = (
130
+ previousSnapshot: MessagesSnapshot,
131
+ ) => {
132
+ const container = messagesContainerRef.current;
133
+ if (!container) return;
134
+
135
+ const scrollHeightDelta = container.scrollHeight - previousSnapshot.scrollHeight;
136
+
137
+ if (scrollHeightDelta !== 0) {
138
+ container.scrollTop += scrollHeightDelta;
139
+ }
140
+ };
141
+
142
+ const handleMessagesScroll = () => {
143
+ const container = messagesContainerRef.current;
144
+ if (!container) return;
145
+
146
+ shouldAutoScrollRef.current = isNearBottom(container);
75
147
  };
76
148
 
77
- useEffect(() => {
78
- scrollToBottom();
79
- }, [messages]);
149
+ useLayoutEffect(() => {
150
+ if (!isOpen) {
151
+ hasInitialScrollRef.current = false;
152
+ shouldAutoScrollRef.current = true;
153
+ previousSnapshotRef.current = null;
154
+ return;
155
+ }
156
+
157
+ const container = messagesContainerRef.current;
158
+ if (!container) return;
159
+
160
+ const nextSnapshot = createMessagesSnapshot();
161
+ const previousSnapshot = previousSnapshotRef.current;
162
+
163
+ if (!hasInitialScrollRef.current) {
164
+ if (messages.length === 0) {
165
+ previousSnapshotRef.current = nextSnapshot;
166
+ return;
167
+ }
168
+
169
+ scrollToBottom("auto");
170
+ hasInitialScrollRef.current = true;
171
+ previousSnapshotRef.current = createMessagesSnapshot();
172
+ return;
173
+ }
174
+
175
+ if (!previousSnapshot) {
176
+ previousSnapshotRef.current = nextSnapshot;
177
+ return;
178
+ }
179
+
180
+ const hasPrependedMessages =
181
+ nextSnapshot.messageCount > previousSnapshot.messageCount &&
182
+ previousSnapshot.firstMessageKey !== null &&
183
+ nextSnapshot.firstMessageKey !== previousSnapshot.firstMessageKey &&
184
+ nextSnapshot.lastMessageKey === previousSnapshot.lastMessageKey;
185
+
186
+ const hasAppendedMessages =
187
+ nextSnapshot.messageCount > previousSnapshot.messageCount &&
188
+ previousSnapshot.lastMessageKey !== null &&
189
+ nextSnapshot.lastMessageKey !== previousSnapshot.lastMessageKey &&
190
+ nextSnapshot.firstMessageKey === previousSnapshot.firstMessageKey;
191
+
192
+ const hasReplacedHistory =
193
+ nextSnapshot.messageCount > 0 &&
194
+ previousSnapshot.messageCount > 0 &&
195
+ nextSnapshot.firstMessageKey !== previousSnapshot.firstMessageKey &&
196
+ nextSnapshot.lastMessageKey !== previousSnapshot.lastMessageKey;
197
+
198
+ if (hasPrependedMessages) {
199
+ preserveScrollPositionAfterPrepend(previousSnapshot);
200
+ } else if (hasAppendedMessages) {
201
+ if (shouldAutoScrollRef.current) {
202
+ scrollToBottom("auto");
203
+ }
204
+ } else if (hasReplacedHistory && shouldAutoScrollRef.current) {
205
+ scrollToBottom("auto");
206
+ }
207
+
208
+ previousSnapshotRef.current = createMessagesSnapshot();
209
+ }, [isOpen, messages]);
80
210
 
81
211
  const handleSendMessage = () => {
82
212
  const message = inputValue.trim();
@@ -112,115 +242,139 @@ export function LiveChat({
112
242
  `}</style>
113
243
 
114
244
  <div
115
- onClick={(e) => e.stopPropagation()}
116
- className={cn(
117
- "fixed inset-0 z-[110] flex flex-col overflow-hidden bg-white dark:bg-app-dark-400 lg:inset-auto lg:right-6 lg:top-[75px] lg:max-w-[340px] lg:max-h-[calc(100vh-90px)] lg:border lg:border-black/10 lg:shadow-2xl lg:dark:border-white/10",
118
- "lg:h-[800px]",
119
- classNames?.container,
120
- )}
121
- style={{ background }}
245
+ className="fixed inset-0 z-[110]"
246
+ onClick={onClose}
247
+ aria-hidden="true"
122
248
  >
123
- {renderHeader ? (
124
- renderHeader()
125
- ) : (
249
+ <div
250
+ onClick={(e) => e.stopPropagation()}
251
+ role="dialog"
252
+ aria-modal="true"
253
+ aria-label={title}
254
+ className={cn(
255
+ "fixed inset-0 flex flex-col overflow-hidden bg-white dark:bg-app-dark-400 lg:inset-auto lg:right-6 lg:top-[75px] lg:max-w-[340px] lg:max-h-[calc(100vh-90px)] lg:border lg:border-black/10 lg:shadow-2xl lg:dark:border-white/10",
256
+ "lg:h-[800px]",
257
+ classNames?.container,
258
+ )}
259
+ style={{ background }}
260
+ >
261
+ {renderHeader ? (
262
+ renderHeader()
263
+ ) : (
264
+ <div
265
+ className={cn(
266
+ "flex items-center justify-between border-b border-black/10 px-4 py-4 dark:border-white/10 lg:px-6",
267
+ classNames?.header,
268
+ )}
269
+ >
270
+ <div className="flex items-center gap-3">
271
+ <h2 className="text-lg font-semibold text-triad-dark-100 dark:text-white">
272
+ {title}
273
+ </h2>
274
+ <div className="flex items-center gap-1.5">
275
+ <div className="size-2 rounded-full bg-green-500 animate-pulse" />
276
+ <span className="text-xs text-app-gray-400 dark:text-white">
277
+ {onlineCount} online
278
+ </span>
279
+ </div>
280
+ </div>
281
+ <button onClick={onClose}>
282
+ <X className="text-black dark:text-white" size={18} />
283
+ </button>
284
+ </div>
285
+ )}
286
+
126
287
  <div
288
+ ref={messagesContainerRef}
289
+ onScroll={handleMessagesScroll}
127
290
  className={cn(
128
- "flex items-center justify-between border-b border-black/10 px-4 py-4 dark:border-white/10 lg:px-6",
129
- classNames?.header,
291
+ "flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6",
292
+ classNames?.messages,
130
293
  )}
294
+ style={{ overflowAnchor: "none" }}
131
295
  >
132
- <div className="flex items-center gap-3">
133
- <h2 className="text-lg font-semibold text-triad-dark-100 dark:text-white">
134
- {title}
135
- </h2>
136
- <div className="flex items-center gap-1.5">
137
- <div className="size-2 rounded-full bg-green-500 animate-pulse" />
138
- <span className="text-xs text-app-gray-400 dark:text-white">
139
- {onlineCount} online
140
- </span>
141
- </div>
142
- </div>
143
- <button onClick={onClose}>
144
- <X className="text-black dark:text-white" size={18} />
145
- </button>
296
+ {(() => {
297
+ const messageOccurrences = new Map<string, number>();
298
+
299
+ return messages.map((message) => {
300
+ const baseKey = getMessageIdentity(message) ?? "message";
301
+ const occurrence = messageOccurrences.get(baseKey) ?? 0;
302
+ const messageKey = `${baseKey}:${occurrence}`;
303
+
304
+ messageOccurrences.set(baseKey, occurrence + 1);
305
+
306
+ return renderMessage ? (
307
+ <React.Fragment key={messageKey}>
308
+ {renderMessage(message)}
309
+ </React.Fragment>
310
+ ) : (
311
+ <div
312
+ key={messageKey}
313
+ className={`flex flex-col gap-1 ${
314
+ message.authority === authority ? "items-end" : "items-start"
315
+ }`}
316
+ >
317
+ {message.authority !== authority && (
318
+ <span className="max-w-[160px] truncate text-xs font-medium text-triad-dark-100 dark:text-white">
319
+ {message.name}
320
+ </span>
321
+ )}
322
+
323
+ <div
324
+ className={cn(
325
+ "max-w-[75%] rounded-2xl px-4 py-2.5",
326
+ message.authority === authority
327
+ ? "rounded-br-sm text-white"
328
+ : "rounded-bl-sm bg-black/5 text-triad-dark-100 dark:bg-white/5 dark:text-white",
329
+ )}
330
+ style={
331
+ message.authority === authority
332
+ ? { backgroundColor: primary }
333
+ : undefined
334
+ }
335
+ >
336
+ <p className="text-sm break-words">{message.message}</p>
337
+ </div>
338
+
339
+ <div className="flex items-center gap-2 text-[10px] text-black opacity-50 dark:text-white">
340
+ <span>{formatPredictionsCount(message.predictionsCount)}</span>
341
+ <span>{format(message.timestamp, "HH:mm")}</span>
342
+ </div>
343
+ </div>
344
+ );
345
+ });
346
+ })()}
146
347
  </div>
147
- )}
148
348
 
149
- <div
150
- className={cn(
151
- "flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6",
152
- classNames?.messages,
153
- )}
154
- >
155
- {messages.map((message, idx) =>
156
- renderMessage ? (
157
- <React.Fragment key={idx}>{renderMessage(message)}</React.Fragment>
158
- ) : (
159
- <div
160
- key={idx}
161
- className={`flex flex-col gap-1 ${
162
- message.authority === authority ? "items-end" : "items-start"
163
- }`}
164
- >
165
- {message.authority !== authority && (
166
- <span className="max-w-[160px] truncate text-xs font-medium text-triad-dark-100 dark:text-white">
167
- {message.name}
168
- </span>
169
- )}
349
+ <div className="border-t border-black/10 px-4 py-4 dark:border-white/10 lg:px-6">
350
+ <div className="flex flex-col gap-2">
351
+ <div className="flex items-center gap-2">
352
+ <input
353
+ value={inputValue}
354
+ onChange={(e) => setInputValue(e.target.value)}
355
+ onKeyDown={handleKeyDown}
356
+ maxLength={CHAT_MAX_MESSAGE_LENGTH}
357
+ placeholder="Digite sua mensagem..."
358
+ className={cn(
359
+ "flex-1 rounded-xl bg-black/5 px-4 py-2.5 text-sm text-app-gray-400 outline-none dark:bg-white/5 dark:text-white",
360
+ classNames?.input,
361
+ )}
362
+ />
170
363
 
171
- <div
364
+ <button
365
+ onClick={handleSendMessage}
366
+ disabled={!inputValue.trim() || (!authority && !onConnectWallet)}
172
367
  className={cn(
173
- "max-w-[75%] rounded-2xl px-4 py-2.5",
174
- message.authority === authority
175
- ? "rounded-br-sm text-white"
176
- : "rounded-bl-sm bg-black/5 text-triad-dark-100 dark:bg-white/5 dark:text-white",
368
+ "flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400",
369
+ classNames?.button,
177
370
  )}
178
- style={
179
- message.authority === authority
180
- ? { backgroundColor: primary }
181
- : undefined
182
- }
371
+ style={{
372
+ opacity: inputValue.trim() ? 1 : 0.5,
373
+ }}
183
374
  >
184
- <p className="text-sm break-words">{message.message}</p>
185
- </div>
186
-
187
- <span className="text-[10px] text-black opacity-50 dark:text-white">
188
- {format(message.timestamp, "HH:mm")}
189
- </span>
375
+ <Send color={theme?.buttonSend.color} size={18} />
376
+ </button>
190
377
  </div>
191
- ),
192
- )}
193
- <div ref={messagesEndRef} />
194
- </div>
195
-
196
- <div className="border-t border-black/10 px-4 py-4 dark:border-white/10 lg:px-6">
197
- <div className="flex flex-col gap-2">
198
- <div className="flex items-center gap-2">
199
- <input
200
- value={inputValue}
201
- onChange={(e) => setInputValue(e.target.value)}
202
- onKeyDown={handleKeyDown}
203
- maxLength={CHAT_MAX_MESSAGE_LENGTH}
204
- placeholder="Digite sua mensagem..."
205
- className={cn(
206
- "flex-1 rounded-xl bg-black/5 px-4 py-2.5 text-sm text-app-gray-400 outline-none dark:bg-white/5 dark:text-white",
207
- classNames?.input,
208
- )}
209
- />
210
-
211
- <button
212
- onClick={handleSendMessage}
213
- disabled={!inputValue.trim() || (!authority && !onConnectWallet)}
214
- className={cn(
215
- "flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400",
216
- classNames?.button,
217
- )}
218
- style={{
219
- opacity: inputValue.trim() ? 1 : 0.5,
220
- }}
221
- >
222
- <Send color={theme?.buttonSend.color} size={18} />
223
- </button>
224
378
  </div>
225
379
  </div>
226
380
  </div>