@triadxyz/widgets 0.0.8 → 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.d.mts CHANGED
@@ -20,6 +20,9 @@ interface LiveChatTheme {
20
20
  primaryColor?: string;
21
21
  background?: string;
22
22
  textColor?: string;
23
+ buttonSend: {
24
+ color: string;
25
+ };
23
26
  }
24
27
  interface LiveChatClassNames {
25
28
  container?: string;
@@ -44,6 +47,6 @@ interface LiveChatProps {
44
47
  description: string;
45
48
  };
46
49
  }
47
- declare function LiveChat({ isOpen, onClose, title, theme, classNames, renderMessage, renderHeader, authority, customerAuthority, fallbackMessages, onConnectWallet, }: LiveChatProps): react_jsx_runtime.JSX.Element | null;
50
+ declare function LiveChat({ isOpen, onClose, title, theme, classNames, renderMessage, renderHeader, authority, customerAuthority, onConnectWallet, }: LiveChatProps): react_jsx_runtime.JSX.Element | null;
48
51
 
49
52
  export { ButtonWidget, type ButtonWidgetProps, LiveChat, type LiveChatClassNames, type LiveChatProps, type LiveChatTheme };
package/dist/index.d.ts CHANGED
@@ -20,6 +20,9 @@ interface LiveChatTheme {
20
20
  primaryColor?: string;
21
21
  background?: string;
22
22
  textColor?: string;
23
+ buttonSend: {
24
+ color: string;
25
+ };
23
26
  }
24
27
  interface LiveChatClassNames {
25
28
  container?: string;
@@ -44,6 +47,6 @@ interface LiveChatProps {
44
47
  description: string;
45
48
  };
46
49
  }
47
- declare function LiveChat({ isOpen, onClose, title, theme, classNames, renderMessage, renderHeader, authority, customerAuthority, fallbackMessages, onConnectWallet, }: LiveChatProps): react_jsx_runtime.JSX.Element | null;
50
+ declare function LiveChat({ isOpen, onClose, title, theme, classNames, renderMessage, renderHeader, authority, customerAuthority, onConnectWallet, }: LiveChatProps): react_jsx_runtime.JSX.Element | null;
48
51
 
49
52
  export { ButtonWidget, type ButtonWidgetProps, LiveChat, type LiveChatClassNames, type LiveChatProps, type LiveChatTheme };
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- 'use strict';var jsxRuntime=require('react/jsx-runtime'),react=require('react'),lucideReact=require('lucide-react'),clsx=require('clsx'),tailwindMerge=require('tailwind-merge'),S=require('socket.io-client'),dateFns=require('date-fns');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var S__default=/*#__PURE__*/_interopDefault(S);var O=({label:r,variant:o="primary",className:u="",...p})=>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"}[o]} ${u}`,...p,children:r});function c(...r){return tailwindMerge.twMerge(clsx.clsx(r))}var T=S__default.default("https://beta.triadfi.co",{path:"/socket.io",reconnection:true,reconnectionAttempts:5,reconnectionDelay:1e3,randomizationFactor:.5,timeout:5e3,transports:["websocket"]}),d=T;function M({authority:r,customerAuthority:o}){let[u,p]=react.useState([]),[n,g]=react.useState(0);return react.useEffect(()=>{if(!r||!o)return;d.emit("chat:join",{authority:r,customerAuthority:o});let s=l=>{p(l);},x=l=>{p(i=>[...i,l]);},m=l=>{g(l.count);};return d.on("chat:history",s),d.on("chat:message",x),d.on("chat:users",m),()=>{d.emit("chat:leave",{authority:r,customerAuthority:o}),d.off("chat:history",s),d.off("chat:message",x),d.off("chat:users",m);}},[r,o]),{messages:u,onlineCount:n,sendMessage:s=>{d.emit("chat:message",{authority:r,customerAuthority:o,message:s},x=>{x.error&&console.error("send error",x.error);});}}}function oe({isOpen:r,onClose:o,title:u="Live Chat",theme:p,classNames:n,renderMessage:g,renderHeader:h,authority:s,customerAuthority:x,fallbackMessages:m,onConnectWallet:l}){let[i,b]=react.useState(""),v=react.useRef(null),f=p?.primaryColor||"#FF3D00",D=p?.background||"var(--chat-bg)",{sendMessage:E,onlineCount:N,messages:y}=M({authority:s||void 0,customerAuthority:x}),H=()=>{v.current?.scrollIntoView({behavior:"smooth"});};react.useEffect(()=>{H();},[y]);let k=()=>{i.trim()&&(b(""),E(i));},C=t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),k());};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:t=>t.stopPropagation(),className:c("fixed right-6 z-[110] hidden lg:flex flex-col overflow-hidden shadow-2xl border border-black/10 dark:border-white/10",n?.container),style:{top:"75px",width:"340px",height:s?"800px":"auto",maxHeight:"calc(100vh - 90px)",background:D},children:[h?h():jsxRuntime.jsxs("div",{className:c("flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10",n?.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:u}),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:[N," online"]})]})]}),jsxRuntime.jsx("button",{onClick:o,children:jsxRuntime.jsx(lucideReact.X,{className:"dark:text-white text-black",size:18})})]}),s&&jsxRuntime.jsxs("div",{className:c("flex-1 overflow-y-auto px-6 py-4 space-y-4",n?.messages),children:[y.map((t,w)=>g?g(t):jsxRuntime.jsxs("div",{className:`flex flex-col gap-1 ${t.authority===s?"items-end":"items-start"}`,children:[t.authority!==s&&jsxRuntime.jsx("span",{className:"text-xs font-medium text-triad-dark-100 dark:text-white truncate max-w-[120px] truncate max-w-[160px]",children:t.name}),jsxRuntime.jsx("div",{className:c("max-w-[75%] rounded-2xl px-4 py-2.5",t.authority===s?"text-white rounded-br-sm":"bg-black/5 dark:bg-white/5 text-triad-dark-100 dark:text-white rounded-bl-sm"),style:t.authority===s?{backgroundColor:f}:void 0,children:jsxRuntime.jsx("p",{className:"text-sm break-words",children:t.message})}),jsxRuntime.jsx("span",{className:"text-[10px] opacity-50 text-black dark:text-white",children:dateFns.format(t.timestamp,"HH:mm")})]},w)),jsxRuntime.jsx("div",{ref:v})]}),jsxRuntime.jsx("div",{className:"px-6 py-4 border-t border-black/10 dark:border-white/10",children:s?jsxRuntime.jsxs("div",{className:"flex items-center gap-2",children:[jsxRuntime.jsx("input",{value:i,onChange:t=>b(t.target.value),onKeyDown:C,placeholder:"Digite sua mensagem...",className:c("flex-1 px-4 py-2.5 rounded-xl bg-black/5 text-app-gray-400 dark:bg-white/5 dark:text-white text-sm outline-none",n?.input)}),jsxRuntime.jsx("button",{onClick:k,disabled:!i.trim(),className:c("flex items-center justify-center size-10 rounded-xl transition-colors",n?.button),style:{backgroundColor:i.trim()?f:void 0,opacity:i.trim()?1:.5},children:jsxRuntime.jsx(lucideReact.Send,{size:18,className:"dark:text-white text-app-gay-400"})})]}):jsxRuntime.jsxs("div",{className:"flex flex-col items-center justify-center gap-3 py-4 px-3 text-center",children:[jsxRuntime.jsx("div",{className:"flex items-center justify-center size-12 rounded-2xl",style:{background:`${f}15`},children:jsxRuntime.jsx("span",{className:"text-xl",children:"\u{1F4AC}"})}),jsxRuntime.jsxs("div",{className:"flex flex-col gap-1",children:[jsxRuntime.jsx("span",{className:"text-sm font-semibold text-black dark:text-white",children:m?.connectWallet||"Entre no chat"}),jsxRuntime.jsx("span",{className:"text-xs text-black/60 dark:text-white max-w-[220px]",children:m?.description||"Conecte sua carteira para participar das conversas em tempo real"})]}),l&&jsxRuntime.jsx("button",{onClick:l,style:{backgroundColor:f},className:"mt-2 w-full max-w-[220px] px-4 py-2.5 text-white text-sm font-semibold rounded-xl transition-all hover:opacity-90 active:scale-95 shadow-sm",children:m?.connectWallet||"Connect Wallet"})]})})]}),jsxRuntime.jsx("div",{className:"fixed inset-0 z-[110] flex flex-col lg:hidden bg-white dark:bg-app-dark-400",children:jsxRuntime.jsxs("div",{className:"flex-1 flex flex-col",children:[jsxRuntime.jsxs("div",{className:c("flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10",n?.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:u}),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:[N," online"]})]})]}),jsxRuntime.jsx("button",{onClick:o,children:jsxRuntime.jsx(lucideReact.X,{className:"dark:text-white text-black",size:18})})]}),jsxRuntime.jsxs("div",{className:"flex-1 overflow-y-auto px-4 py-4 space-y-4",children:[y.map((t,w)=>jsxRuntime.jsx("div",{children:t.message},w)),jsxRuntime.jsx("div",{ref:v})]}),jsxRuntime.jsx("div",{className:"p-4 border-t flex flex-col gap-2",children:s?jsxRuntime.jsxs("div",{className:"flex gap-2 w-full",children:[jsxRuntime.jsx("input",{value:i,onChange:t=>b(t.target.value),onKeyDown:C,placeholder:"Digite sua mensagem...",className:c("flex-1 px-4 py-2.5 rounded-xl bg-black/5 text-app-gay-400 dark:bg-white/5 dark:text-white text-sm outline-none",n?.input)}),jsxRuntime.jsx("button",{className:c("flex items-center justify-center size-10 rounded-xl transition-colors",n?.button),style:{backgroundColor:i.trim()?f:void 0,opacity:i.trim()?1:.5},onClick:k,children:jsxRuntime.jsx(lucideReact.Send,{className:"dark:text-white text-app-gay-400",size:18})})]}):jsxRuntime.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 py-2",children:[jsxRuntime.jsx("span",{className:"text-sm font-medium text-black/60 dark:text-white/60 text-center",children:m?.description||"Fa\xE7a login para participar do chat"}),l&&jsxRuntime.jsx("button",{onClick:l,style:{backgroundColor:f},className:"px-4 py-2 text-white text-sm font-semibold rounded-xl transition-opacity hover:opacity-90 active:scale-95",children:m?.connectWallet||"Fazer Login"})]})})]})})]}):null}exports.ButtonWidget=O;exports.LiveChat=oe;//# 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","useEffect","handleHistory","msgs","handleMessage","msg","prev","handleUsers","data","message","res","LiveChat","isOpen","onClose","title","theme","classNames","renderMessage","renderHeader","fallbackMessages","onConnectWallet","inputValue","setInputValue","messagesEndRef","useRef","primary","background","sendMessage","scrollToBottom","handleSendMessage","handleKeyDown","e","jsxs","Fragment","X","idx","format","Send","m"],"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\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\n useEffect(() => {\n if (!authority || !customerAuthority) return;\n\n socket.emit(\"chat:join\", { authority, customerAuthority });\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: any) => {\n setOnlineCount(data.count);\n };\n\n socket.on(\"chat:history\", handleHistory);\n socket.on(\"chat:message\", handleMessage);\n socket.on(\"chat:users\", handleUsers);\n\n return () => {\n socket.emit(\"chat:leave\", { authority, customerAuthority });\n\n socket.off(\"chat:history\", handleHistory);\n socket.off(\"chat:message\", handleMessage);\n socket.off(\"chat:users\", handleUsers);\n };\n }, [authority, customerAuthority]);\n\n const sendMessage = (message: string) => {\n socket.emit(\n \"chat:message\",\n { authority, customerAuthority, message },\n (res: any) => {\n if (res.error) {\n console.error(\"send error\", res.error);\n }\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}\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\nexport function LiveChat({\n isOpen,\n onClose,\n title = \"Live Chat\",\n theme,\n classNames,\n renderMessage,\n renderHeader,\n authority,\n customerAuthority,\n fallbackMessages,\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 if (!inputValue.trim()) return;\n setInputValue(\"\");\n sendMessage(inputValue);\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 {/* Desktop */}\n <div\n onClick={(e) => e.stopPropagation()}\n className={cn(\n \"fixed right-6 z-[110] hidden lg:flex flex-col overflow-hidden shadow-2xl border border-black/10 dark:border-white/10\",\n classNames?.container,\n )}\n style={{\n top: \"75px\",\n width: \"340px\",\n height: !authority ? \"auto\" : \"800px\",\n maxHeight: \"calc(100vh - 90px)\",\n background,\n }}\n >\n {/* Header */}\n {renderHeader ? (\n renderHeader()\n ) : (\n <div\n className={cn(\n \"flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10\",\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=\"dark:text-white text-black\" size={18} />\n </button>\n </div>\n )}\n\n {/* Messages */}\n {authority && (\n <div\n className={cn(\n \"flex-1 overflow-y-auto px-6 py-4 space-y-4\",\n classNames?.messages,\n )}\n >\n {messages.map((message, idx) =>\n renderMessage ? (\n renderMessage(message)\n ) : (\n <div\n key={idx}\n className={`flex flex-col gap-1 ${\n message.authority === authority\n ? \"items-end\"\n : \"items-start\"\n }`}\n >\n {message.authority !== authority && (\n <span className=\"text-xs font-medium text-triad-dark-100 dark:text-white truncate max-w-[120px] truncate max-w-[160px]\">\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 ? \"text-white rounded-br-sm\"\n : \"bg-black/5 dark:bg-white/5 text-triad-dark-100 dark:text-white rounded-bl-sm\",\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] opacity-50 text-black dark:text-white\">\n {format(message.timestamp, \"HH:mm\")}\n </span>\n </div>\n ),\n )}\n <div ref={messagesEndRef} />\n </div>\n )}\n {/* Input */}\n <div className=\"px-6 py-4 border-t border-black/10 dark:border-white/10\">\n {!authority ? (\n <div className=\"flex flex-col items-center justify-center gap-3 py-4 px-3 text-center\">\n {/* Icon */}\n <div\n className=\"flex items-center justify-center size-12 rounded-2xl\"\n style={{\n background: `${primary}15`,\n }}\n >\n <span className=\"text-xl\">💬</span>\n </div>\n\n {/* Text */}\n <div className=\"flex flex-col gap-1\">\n <span className=\"text-sm font-semibold text-black dark:text-white\">\n {fallbackMessages?.connectWallet || \"Entre no chat\"}\n </span>\n <span className=\"text-xs text-black/60 dark:text-white max-w-[220px]\">\n {fallbackMessages?.description ||\n \"Conecte sua carteira para participar das conversas em tempo real\"}\n </span>\n </div>\n\n {/* Button */}\n {onConnectWallet && (\n <button\n onClick={onConnectWallet}\n style={{ backgroundColor: primary }}\n className=\"mt-2 w-full max-w-[220px] px-4 py-2.5 text-white text-sm font-semibold rounded-xl transition-all hover:opacity-90 active:scale-95 shadow-sm\"\n >\n {fallbackMessages?.connectWallet || \"Connect Wallet\"}\n </button>\n )}\n </div>\n ) : (\n <div className=\"flex items-center gap-2\">\n <input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"Digite sua mensagem...\"\n className={cn(\n \"flex-1 px-4 py-2.5 rounded-xl bg-black/5 text-app-gray-400 dark:bg-white/5 dark:text-white text-sm outline-none\",\n classNames?.input,\n )}\n />\n\n <button\n onClick={handleSendMessage}\n disabled={!inputValue.trim()}\n className={cn(\n \"flex items-center justify-center size-10 rounded-xl transition-colors\",\n classNames?.button,\n )}\n style={{\n backgroundColor: inputValue.trim() ? primary : undefined,\n opacity: inputValue.trim() ? 1 : 0.5,\n }}\n >\n <Send size={18} className=\"dark:text-white text-app-gay-400\" />\n </button>\n </div>\n )}\n </div>\n </div>\n\n {/* Mobile */}\n <div className=\"fixed inset-0 z-[110] flex flex-col lg:hidden bg-white dark:bg-app-dark-400\">\n {/* Reaproveita mesma estrutura */}\n <div className=\"flex-1 flex flex-col\">\n {/* Header */}\n <div\n className={cn(\n \"flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10\",\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=\"dark:text-white text-black\" size={18} />\n </button>\n </div>\n\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto px-4 py-4 space-y-4\">\n {messages.map((m, idx) => (\n <div key={idx}>{m.message}</div>\n ))}\n <div ref={messagesEndRef} />\n </div>\n\n {/* Input */}\n <div className=\"p-4 border-t flex flex-col gap-2\">\n {!authority ? (\n <div className=\"flex-1 flex flex-col items-center justify-center gap-2 py-2\">\n <span className=\"text-sm font-medium text-black/60 dark:text-white/60 text-center\">\n {fallbackMessages?.description ||\n \"Faça login para participar do chat\"}\n </span>\n {onConnectWallet && (\n <button\n onClick={onConnectWallet}\n style={{ backgroundColor: primary }}\n className=\"px-4 py-2 text-white text-sm font-semibold rounded-xl transition-opacity hover:opacity-90 active:scale-95\"\n >\n {fallbackMessages?.connectWallet || \"Fazer Login\"}\n </button>\n )}\n </div>\n ) : (\n <div className=\"flex gap-2 w-full\">\n <input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"Digite sua mensagem...\"\n className={cn(\n \"flex-1 px-4 py-2.5 rounded-xl bg-black/5 text-app-gay-400 dark:bg-white/5 dark:text-white text-sm outline-none\",\n classNames?.input,\n )}\n />\n <button\n className={cn(\n \"flex items-center justify-center size-10 rounded-xl transition-colors\",\n classNames?.button,\n )}\n style={{\n backgroundColor: inputValue.trim() ? primary : undefined,\n opacity: inputValue.trim() ? 1 : 0.5,\n }}\n onClick={handleSendMessage}\n >\n <Send\n className=\"dark:text-white text-app-gay-400\"\n size={18}\n />\n </button>\n </div>\n )}\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.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 {useState,useRef,useEffect}from'react';import {X,Send}from'lucide-react';import {clsx}from'clsx';import {twMerge}from'tailwind-merge';import S from'socket.io-client';import {format}from'date-fns';var O=({label:r,variant:o="primary",className:u="",...p})=>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"}[o]} ${u}`,...p,children:r});function c(...r){return twMerge(clsx(r))}var T=S("https://beta.triadfi.co",{path:"/socket.io",reconnection:true,reconnectionAttempts:5,reconnectionDelay:1e3,randomizationFactor:.5,timeout:5e3,transports:["websocket"]}),d=T;function M({authority:r,customerAuthority:o}){let[u,p]=useState([]),[n,g]=useState(0);return useEffect(()=>{if(!r||!o)return;d.emit("chat:join",{authority:r,customerAuthority:o});let s=l=>{p(l);},x=l=>{p(i=>[...i,l]);},m=l=>{g(l.count);};return d.on("chat:history",s),d.on("chat:message",x),d.on("chat:users",m),()=>{d.emit("chat:leave",{authority:r,customerAuthority:o}),d.off("chat:history",s),d.off("chat:message",x),d.off("chat:users",m);}},[r,o]),{messages:u,onlineCount:n,sendMessage:s=>{d.emit("chat:message",{authority:r,customerAuthority:o,message:s},x=>{x.error&&console.error("send error",x.error);});}}}function oe({isOpen:r,onClose:o,title:u="Live Chat",theme:p,classNames:n,renderMessage:g,renderHeader:h,authority:s,customerAuthority:x,fallbackMessages:m,onConnectWallet:l}){let[i,b]=useState(""),v=useRef(null),f=p?.primaryColor||"#FF3D00",D=p?.background||"var(--chat-bg)",{sendMessage:E,onlineCount:N,messages:y}=M({authority:s||void 0,customerAuthority:x}),H=()=>{v.current?.scrollIntoView({behavior:"smooth"});};useEffect(()=>{H();},[y]);let k=()=>{i.trim()&&(b(""),E(i));},C=t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),k());};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:t=>t.stopPropagation(),className:c("fixed right-6 z-[110] hidden lg:flex flex-col overflow-hidden shadow-2xl border border-black/10 dark:border-white/10",n?.container),style:{top:"75px",width:"340px",height:s?"800px":"auto",maxHeight:"calc(100vh - 90px)",background:D},children:[h?h():jsxs("div",{className:c("flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10",n?.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:u}),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:[N," online"]})]})]}),jsx("button",{onClick:o,children:jsx(X,{className:"dark:text-white text-black",size:18})})]}),s&&jsxs("div",{className:c("flex-1 overflow-y-auto px-6 py-4 space-y-4",n?.messages),children:[y.map((t,w)=>g?g(t):jsxs("div",{className:`flex flex-col gap-1 ${t.authority===s?"items-end":"items-start"}`,children:[t.authority!==s&&jsx("span",{className:"text-xs font-medium text-triad-dark-100 dark:text-white truncate max-w-[120px] truncate max-w-[160px]",children:t.name}),jsx("div",{className:c("max-w-[75%] rounded-2xl px-4 py-2.5",t.authority===s?"text-white rounded-br-sm":"bg-black/5 dark:bg-white/5 text-triad-dark-100 dark:text-white rounded-bl-sm"),style:t.authority===s?{backgroundColor:f}:void 0,children:jsx("p",{className:"text-sm break-words",children:t.message})}),jsx("span",{className:"text-[10px] opacity-50 text-black dark:text-white",children:format(t.timestamp,"HH:mm")})]},w)),jsx("div",{ref:v})]}),jsx("div",{className:"px-6 py-4 border-t border-black/10 dark:border-white/10",children:s?jsxs("div",{className:"flex items-center gap-2",children:[jsx("input",{value:i,onChange:t=>b(t.target.value),onKeyDown:C,placeholder:"Digite sua mensagem...",className:c("flex-1 px-4 py-2.5 rounded-xl bg-black/5 text-app-gray-400 dark:bg-white/5 dark:text-white text-sm outline-none",n?.input)}),jsx("button",{onClick:k,disabled:!i.trim(),className:c("flex items-center justify-center size-10 rounded-xl transition-colors",n?.button),style:{backgroundColor:i.trim()?f:void 0,opacity:i.trim()?1:.5},children:jsx(Send,{size:18,className:"dark:text-white text-app-gay-400"})})]}):jsxs("div",{className:"flex flex-col items-center justify-center gap-3 py-4 px-3 text-center",children:[jsx("div",{className:"flex items-center justify-center size-12 rounded-2xl",style:{background:`${f}15`},children:jsx("span",{className:"text-xl",children:"\u{1F4AC}"})}),jsxs("div",{className:"flex flex-col gap-1",children:[jsx("span",{className:"text-sm font-semibold text-black dark:text-white",children:m?.connectWallet||"Entre no chat"}),jsx("span",{className:"text-xs text-black/60 dark:text-white max-w-[220px]",children:m?.description||"Conecte sua carteira para participar das conversas em tempo real"})]}),l&&jsx("button",{onClick:l,style:{backgroundColor:f},className:"mt-2 w-full max-w-[220px] px-4 py-2.5 text-white text-sm font-semibold rounded-xl transition-all hover:opacity-90 active:scale-95 shadow-sm",children:m?.connectWallet||"Connect Wallet"})]})})]}),jsx("div",{className:"fixed inset-0 z-[110] flex flex-col lg:hidden bg-white dark:bg-app-dark-400",children:jsxs("div",{className:"flex-1 flex flex-col",children:[jsxs("div",{className:c("flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10",n?.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:u}),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:[N," online"]})]})]}),jsx("button",{onClick:o,children:jsx(X,{className:"dark:text-white text-black",size:18})})]}),jsxs("div",{className:"flex-1 overflow-y-auto px-4 py-4 space-y-4",children:[y.map((t,w)=>jsx("div",{children:t.message},w)),jsx("div",{ref:v})]}),jsx("div",{className:"p-4 border-t flex flex-col gap-2",children:s?jsxs("div",{className:"flex gap-2 w-full",children:[jsx("input",{value:i,onChange:t=>b(t.target.value),onKeyDown:C,placeholder:"Digite sua mensagem...",className:c("flex-1 px-4 py-2.5 rounded-xl bg-black/5 text-app-gay-400 dark:bg-white/5 dark:text-white text-sm outline-none",n?.input)}),jsx("button",{className:c("flex items-center justify-center size-10 rounded-xl transition-colors",n?.button),style:{backgroundColor:i.trim()?f:void 0,opacity:i.trim()?1:.5},onClick:k,children:jsx(Send,{className:"dark:text-white text-app-gay-400",size:18})})]}):jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 py-2",children:[jsx("span",{className:"text-sm font-medium text-black/60 dark:text-white/60 text-center",children:m?.description||"Fa\xE7a login para participar do chat"}),l&&jsx("button",{onClick:l,style:{backgroundColor:f},className:"px-4 py-2 text-white text-sm font-semibold rounded-xl transition-opacity hover:opacity-90 active:scale-95",children:m?.connectWallet||"Fazer Login"})]})})]})})]}):null}export{O as ButtonWidget,oe 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","useEffect","handleHistory","msgs","handleMessage","msg","prev","handleUsers","data","message","res","LiveChat","isOpen","onClose","title","theme","classNames","renderMessage","renderHeader","fallbackMessages","onConnectWallet","inputValue","setInputValue","messagesEndRef","useRef","primary","background","sendMessage","scrollToBottom","handleSendMessage","handleKeyDown","e","jsxs","Fragment","X","idx","format","Send","m"],"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\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\n useEffect(() => {\n if (!authority || !customerAuthority) return;\n\n socket.emit(\"chat:join\", { authority, customerAuthority });\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: any) => {\n setOnlineCount(data.count);\n };\n\n socket.on(\"chat:history\", handleHistory);\n socket.on(\"chat:message\", handleMessage);\n socket.on(\"chat:users\", handleUsers);\n\n return () => {\n socket.emit(\"chat:leave\", { authority, customerAuthority });\n\n socket.off(\"chat:history\", handleHistory);\n socket.off(\"chat:message\", handleMessage);\n socket.off(\"chat:users\", handleUsers);\n };\n }, [authority, customerAuthority]);\n\n const sendMessage = (message: string) => {\n socket.emit(\n \"chat:message\",\n { authority, customerAuthority, message },\n (res: any) => {\n if (res.error) {\n console.error(\"send error\", res.error);\n }\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}\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\nexport function LiveChat({\n isOpen,\n onClose,\n title = \"Live Chat\",\n theme,\n classNames,\n renderMessage,\n renderHeader,\n authority,\n customerAuthority,\n fallbackMessages,\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 if (!inputValue.trim()) return;\n setInputValue(\"\");\n sendMessage(inputValue);\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 {/* Desktop */}\n <div\n onClick={(e) => e.stopPropagation()}\n className={cn(\n \"fixed right-6 z-[110] hidden lg:flex flex-col overflow-hidden shadow-2xl border border-black/10 dark:border-white/10\",\n classNames?.container,\n )}\n style={{\n top: \"75px\",\n width: \"340px\",\n height: !authority ? \"auto\" : \"800px\",\n maxHeight: \"calc(100vh - 90px)\",\n background,\n }}\n >\n {/* Header */}\n {renderHeader ? (\n renderHeader()\n ) : (\n <div\n className={cn(\n \"flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10\",\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=\"dark:text-white text-black\" size={18} />\n </button>\n </div>\n )}\n\n {/* Messages */}\n {authority && (\n <div\n className={cn(\n \"flex-1 overflow-y-auto px-6 py-4 space-y-4\",\n classNames?.messages,\n )}\n >\n {messages.map((message, idx) =>\n renderMessage ? (\n renderMessage(message)\n ) : (\n <div\n key={idx}\n className={`flex flex-col gap-1 ${\n message.authority === authority\n ? \"items-end\"\n : \"items-start\"\n }`}\n >\n {message.authority !== authority && (\n <span className=\"text-xs font-medium text-triad-dark-100 dark:text-white truncate max-w-[120px] truncate max-w-[160px]\">\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 ? \"text-white rounded-br-sm\"\n : \"bg-black/5 dark:bg-white/5 text-triad-dark-100 dark:text-white rounded-bl-sm\",\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] opacity-50 text-black dark:text-white\">\n {format(message.timestamp, \"HH:mm\")}\n </span>\n </div>\n ),\n )}\n <div ref={messagesEndRef} />\n </div>\n )}\n {/* Input */}\n <div className=\"px-6 py-4 border-t border-black/10 dark:border-white/10\">\n {!authority ? (\n <div className=\"flex flex-col items-center justify-center gap-3 py-4 px-3 text-center\">\n {/* Icon */}\n <div\n className=\"flex items-center justify-center size-12 rounded-2xl\"\n style={{\n background: `${primary}15`,\n }}\n >\n <span className=\"text-xl\">💬</span>\n </div>\n\n {/* Text */}\n <div className=\"flex flex-col gap-1\">\n <span className=\"text-sm font-semibold text-black dark:text-white\">\n {fallbackMessages?.connectWallet || \"Entre no chat\"}\n </span>\n <span className=\"text-xs text-black/60 dark:text-white max-w-[220px]\">\n {fallbackMessages?.description ||\n \"Conecte sua carteira para participar das conversas em tempo real\"}\n </span>\n </div>\n\n {/* Button */}\n {onConnectWallet && (\n <button\n onClick={onConnectWallet}\n style={{ backgroundColor: primary }}\n className=\"mt-2 w-full max-w-[220px] px-4 py-2.5 text-white text-sm font-semibold rounded-xl transition-all hover:opacity-90 active:scale-95 shadow-sm\"\n >\n {fallbackMessages?.connectWallet || \"Connect Wallet\"}\n </button>\n )}\n </div>\n ) : (\n <div className=\"flex items-center gap-2\">\n <input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"Digite sua mensagem...\"\n className={cn(\n \"flex-1 px-4 py-2.5 rounded-xl bg-black/5 text-app-gray-400 dark:bg-white/5 dark:text-white text-sm outline-none\",\n classNames?.input,\n )}\n />\n\n <button\n onClick={handleSendMessage}\n disabled={!inputValue.trim()}\n className={cn(\n \"flex items-center justify-center size-10 rounded-xl transition-colors\",\n classNames?.button,\n )}\n style={{\n backgroundColor: inputValue.trim() ? primary : undefined,\n opacity: inputValue.trim() ? 1 : 0.5,\n }}\n >\n <Send size={18} className=\"dark:text-white text-app-gay-400\" />\n </button>\n </div>\n )}\n </div>\n </div>\n\n {/* Mobile */}\n <div className=\"fixed inset-0 z-[110] flex flex-col lg:hidden bg-white dark:bg-app-dark-400\">\n {/* Reaproveita mesma estrutura */}\n <div className=\"flex-1 flex flex-col\">\n {/* Header */}\n <div\n className={cn(\n \"flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10\",\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=\"dark:text-white text-black\" size={18} />\n </button>\n </div>\n\n {/* Messages */}\n <div className=\"flex-1 overflow-y-auto px-4 py-4 space-y-4\">\n {messages.map((m, idx) => (\n <div key={idx}>{m.message}</div>\n ))}\n <div ref={messagesEndRef} />\n </div>\n\n {/* Input */}\n <div className=\"p-4 border-t flex flex-col gap-2\">\n {!authority ? (\n <div className=\"flex-1 flex flex-col items-center justify-center gap-2 py-2\">\n <span className=\"text-sm font-medium text-black/60 dark:text-white/60 text-center\">\n {fallbackMessages?.description ||\n \"Faça login para participar do chat\"}\n </span>\n {onConnectWallet && (\n <button\n onClick={onConnectWallet}\n style={{ backgroundColor: primary }}\n className=\"px-4 py-2 text-white text-sm font-semibold rounded-xl transition-opacity hover:opacity-90 active:scale-95\"\n >\n {fallbackMessages?.connectWallet || \"Fazer Login\"}\n </button>\n )}\n </div>\n ) : (\n <div className=\"flex gap-2 w-full\">\n <input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"Digite sua mensagem...\"\n className={cn(\n \"flex-1 px-4 py-2.5 rounded-xl bg-black/5 text-app-gay-400 dark:bg-white/5 dark:text-white text-sm outline-none\",\n classNames?.input,\n )}\n />\n <button\n className={cn(\n \"flex items-center justify-center size-10 rounded-xl transition-colors\",\n classNames?.button,\n )}\n style={{\n backgroundColor: inputValue.trim() ? primary : undefined,\n opacity: inputValue.trim() ? 1 : 0.5,\n }}\n onClick={handleSendMessage}\n >\n <Send\n className=\"dark:text-white text-app-gay-400\"\n size={18}\n />\n </button>\n </div>\n )}\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.8",
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";
@@ -10,6 +10,9 @@ export interface LiveChatTheme {
10
10
  primaryColor?: string;
11
11
  background?: string;
12
12
  textColor?: string;
13
+ buttonSend: {
14
+ color: string;
15
+ }
13
16
  }
14
17
 
15
18
  export interface LiveChatClassNames {
@@ -43,6 +46,38 @@ export interface LiveChatProps {
43
46
  };
44
47
  }
45
48
 
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
+ }
80
+
46
81
  export function LiveChat({
47
82
  isOpen,
48
83
  onClose,
@@ -53,11 +88,13 @@ export function LiveChat({
53
88
  renderHeader,
54
89
  authority,
55
90
  customerAuthority,
56
- fallbackMessages,
57
91
  onConnectWallet,
58
92
  }: LiveChatProps) {
59
93
  const [inputValue, setInputValue] = useState("");
60
- 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);
61
98
  const primary = theme?.primaryColor || "#FF3D00";
62
99
  const background = theme?.background || "var(--chat-bg)";
63
100
 
@@ -66,18 +103,122 @@ export function LiveChat({
66
103
  customerAuthority: customerAuthority,
67
104
  });
68
105
 
69
- const scrollToBottom = () => {
70
- 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);
71
147
  };
72
148
 
73
- useEffect(() => {
74
- scrollToBottom();
75
- }, [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]);
76
210
 
77
211
  const handleSendMessage = () => {
78
- if (!inputValue.trim()) return;
212
+ const message = inputValue.trim();
213
+ if (!message) return;
214
+
215
+ if (!authority) {
216
+ onConnectWallet?.();
217
+ return;
218
+ }
219
+
79
220
  setInputValue("");
80
- sendMessage(inputValue);
221
+ sendMessage(message);
81
222
  };
82
223
 
83
224
  const handleKeyDown = (e: React.KeyboardEvent) => {
@@ -100,250 +241,141 @@ export function LiveChat({
100
241
  }
101
242
  `}</style>
102
243
 
103
- {/* Desktop */}
104
244
  <div
105
- onClick={(e) => e.stopPropagation()}
106
- className={cn(
107
- "fixed right-6 z-[110] hidden lg:flex flex-col overflow-hidden shadow-2xl border border-black/10 dark:border-white/10",
108
- classNames?.container,
109
- )}
110
- style={{
111
- top: "75px",
112
- width: "340px",
113
- height: !authority ? "auto" : "800px",
114
- maxHeight: "calc(100vh - 90px)",
115
- background,
116
- }}
245
+ className="fixed inset-0 z-[110]"
246
+ onClick={onClose}
247
+ aria-hidden="true"
117
248
  >
118
- {/* Header */}
119
- {renderHeader ? (
120
- renderHeader()
121
- ) : (
122
- <div
123
- className={cn(
124
- "flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10",
125
- classNames?.header,
126
- )}
127
- >
128
- <div className="flex items-center gap-3">
129
- <h2 className="text-lg font-semibold text-triad-dark-100 dark:text-white">
130
- {title}
131
- </h2>
132
- <div className="flex items-center gap-1.5">
133
- <div className="size-2 rounded-full bg-green-500 animate-pulse" />
134
- <span className="text-xs text-app-gray-400 dark:text-white">
135
- {onlineCount} online
136
- </span>
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>
137
280
  </div>
281
+ <button onClick={onClose}>
282
+ <X className="text-black dark:text-white" size={18} />
283
+ </button>
138
284
  </div>
139
- <button onClick={onClose}>
140
- <X className="dark:text-white text-black" size={18} />
141
- </button>
142
- </div>
143
- )}
285
+ )}
144
286
 
145
- {/* Messages */}
146
- {authority && (
147
287
  <div
288
+ ref={messagesContainerRef}
289
+ onScroll={handleMessagesScroll}
148
290
  className={cn(
149
- "flex-1 overflow-y-auto px-6 py-4 space-y-4",
291
+ "flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6",
150
292
  classNames?.messages,
151
293
  )}
294
+ style={{ overflowAnchor: "none" }}
152
295
  >
153
- {messages.map((message, idx) =>
154
- renderMessage ? (
155
- renderMessage(message)
156
- ) : (
157
- <div
158
- key={idx}
159
- className={`flex flex-col gap-1 ${
160
- message.authority === authority
161
- ? "items-end"
162
- : "items-start"
163
- }`}
164
- >
165
- {message.authority !== authority && (
166
- <span className="text-xs font-medium text-triad-dark-100 dark:text-white truncate max-w-[120px] truncate max-w-[160px]">
167
- {message.name}
168
- </span>
169
- )}
170
-
171
- <div
172
- className={cn(
173
- "max-w-[75%] rounded-2xl px-4 py-2.5",
174
- message.authority === authority
175
- ? "text-white rounded-br-sm"
176
- : "bg-black/5 dark:bg-white/5 text-triad-dark-100 dark:text-white rounded-bl-sm",
177
- )}
178
- style={
179
- message.authority === authority
180
- ? { backgroundColor: primary }
181
- : undefined
182
- }
183
- >
184
- <p className="text-sm break-words">{message.message}</p>
185
- </div>
296
+ {(() => {
297
+ const messageOccurrences = new Map<string, number>();
186
298
 
187
- <span className="text-[10px] opacity-50 text-black dark:text-white">
188
- {format(message.timestamp, "HH:mm")}
189
- </span>
190
- </div>
191
- ),
192
- )}
193
- <div ref={messagesEndRef} />
194
- </div>
195
- )}
196
- {/* Input */}
197
- <div className="px-6 py-4 border-t border-black/10 dark:border-white/10">
198
- {!authority ? (
199
- <div className="flex flex-col items-center justify-center gap-3 py-4 px-3 text-center">
200
- {/* Icon */}
201
- <div
202
- className="flex items-center justify-center size-12 rounded-2xl"
203
- style={{
204
- background: `${primary}15`,
205
- }}
206
- >
207
- <span className="text-xl">💬</span>
208
- </div>
299
+ return messages.map((message) => {
300
+ const baseKey = getMessageIdentity(message) ?? "message";
301
+ const occurrence = messageOccurrences.get(baseKey) ?? 0;
302
+ const messageKey = `${baseKey}:${occurrence}`;
209
303
 
210
- {/* Text */}
211
- <div className="flex flex-col gap-1">
212
- <span className="text-sm font-semibold text-black dark:text-white">
213
- {fallbackMessages?.connectWallet || "Entre no chat"}
214
- </span>
215
- <span className="text-xs text-black/60 dark:text-white max-w-[220px]">
216
- {fallbackMessages?.description ||
217
- "Conecte sua carteira para participar das conversas em tempo real"}
218
- </span>
219
- </div>
304
+ messageOccurrences.set(baseKey, occurrence + 1);
220
305
 
221
- {/* Button */}
222
- {onConnectWallet && (
223
- <button
224
- onClick={onConnectWallet}
225
- style={{ backgroundColor: primary }}
226
- className="mt-2 w-full max-w-[220px] px-4 py-2.5 text-white text-sm font-semibold rounded-xl transition-all hover:opacity-90 active:scale-95 shadow-sm"
227
- >
228
- {fallbackMessages?.connectWallet || "Connect Wallet"}
229
- </button>
230
- )}
231
- </div>
232
- ) : (
233
- <div className="flex items-center gap-2">
234
- <input
235
- value={inputValue}
236
- onChange={(e) => setInputValue(e.target.value)}
237
- onKeyDown={handleKeyDown}
238
- placeholder="Digite sua mensagem..."
239
- className={cn(
240
- "flex-1 px-4 py-2.5 rounded-xl bg-black/5 text-app-gray-400 dark:bg-white/5 dark:text-white text-sm outline-none",
241
- classNames?.input,
242
- )}
243
- />
244
-
245
- <button
246
- onClick={handleSendMessage}
247
- disabled={!inputValue.trim()}
248
- className={cn(
249
- "flex items-center justify-center size-10 rounded-xl transition-colors",
250
- classNames?.button,
251
- )}
252
- style={{
253
- backgroundColor: inputValue.trim() ? primary : undefined,
254
- opacity: inputValue.trim() ? 1 : 0.5,
255
- }}
256
- >
257
- <Send size={18} className="dark:text-white text-app-gay-400" />
258
- </button>
259
- </div>
260
- )}
261
- </div>
262
- </div>
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
+ )}
263
322
 
264
- {/* Mobile */}
265
- <div className="fixed inset-0 z-[110] flex flex-col lg:hidden bg-white dark:bg-app-dark-400">
266
- {/* Reaproveita mesma estrutura */}
267
- <div className="flex-1 flex flex-col">
268
- {/* Header */}
269
- <div
270
- className={cn(
271
- "flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10",
272
- classNames?.header,
273
- )}
274
- >
275
- <div className="flex items-center gap-3">
276
- <h2 className="text-lg font-semibold text-triad-dark-100 dark:text-white">
277
- {title}
278
- </h2>
279
- <div className="flex items-center gap-1.5">
280
- <div className="size-2 rounded-full bg-green-500 animate-pulse" />
281
- <span className="text-xs text-app-gray-400 dark:text-white">
282
- {onlineCount} online
283
- </span>
284
- </div>
285
- </div>
286
- <button onClick={onClose}>
287
- <X className="dark:text-white text-black" size={18} />
288
- </button>
289
- </div>
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>
290
338
 
291
- {/* Messages */}
292
- <div className="flex-1 overflow-y-auto px-4 py-4 space-y-4">
293
- {messages.map((m, idx) => (
294
- <div key={idx}>{m.message}</div>
295
- ))}
296
- <div ref={messagesEndRef} />
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
+ })()}
297
347
  </div>
298
348
 
299
- {/* Input */}
300
- <div className="p-4 border-t flex flex-col gap-2">
301
- {!authority ? (
302
- <div className="flex-1 flex flex-col items-center justify-center gap-2 py-2">
303
- <span className="text-sm font-medium text-black/60 dark:text-white/60 text-center">
304
- {fallbackMessages?.description ||
305
- "Faça login para participar do chat"}
306
- </span>
307
- {onConnectWallet && (
308
- <button
309
- onClick={onConnectWallet}
310
- style={{ backgroundColor: primary }}
311
- className="px-4 py-2 text-white text-sm font-semibold rounded-xl transition-opacity hover:opacity-90 active:scale-95"
312
- >
313
- {fallbackMessages?.connectWallet || "Fazer Login"}
314
- </button>
315
- )}
316
- </div>
317
- ) : (
318
- <div className="flex gap-2 w-full">
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">
319
352
  <input
320
353
  value={inputValue}
321
354
  onChange={(e) => setInputValue(e.target.value)}
322
355
  onKeyDown={handleKeyDown}
356
+ maxLength={CHAT_MAX_MESSAGE_LENGTH}
323
357
  placeholder="Digite sua mensagem..."
324
358
  className={cn(
325
- "flex-1 px-4 py-2.5 rounded-xl bg-black/5 text-app-gay-400 dark:bg-white/5 dark:text-white text-sm outline-none",
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",
326
360
  classNames?.input,
327
361
  )}
328
362
  />
363
+
329
364
  <button
365
+ onClick={handleSendMessage}
366
+ disabled={!inputValue.trim() || (!authority && !onConnectWallet)}
330
367
  className={cn(
331
- "flex items-center justify-center size-10 rounded-xl transition-colors",
368
+ "flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400",
332
369
  classNames?.button,
333
370
  )}
334
371
  style={{
335
- backgroundColor: inputValue.trim() ? primary : undefined,
336
372
  opacity: inputValue.trim() ? 1 : 0.5,
337
373
  }}
338
- onClick={handleSendMessage}
339
374
  >
340
- <Send
341
- className="dark:text-white text-app-gay-400"
342
- size={18}
343
- />
375
+ <Send color={theme?.buttonSend.color} size={18} />
344
376
  </button>
345
377
  </div>
346
- )}
378
+ </div>
347
379
  </div>
348
380
  </div>
349
381
  </div>
@@ -10,6 +10,14 @@ export interface ChatMessage {
10
10
  timestamp: number;
11
11
  }
12
12
 
13
+ interface ChatUsersPayload {
14
+ count: number;
15
+ }
16
+
17
+ interface ChatResponse {
18
+ error: string | null;
19
+ }
20
+
13
21
  export function useChat({
14
22
  authority,
15
23
  customerAuthority,
@@ -19,11 +27,10 @@ export function useChat({
19
27
  }) {
20
28
  const [messages, setMessages] = useState<ChatMessage[]>([]);
21
29
  const [onlineCount, setOnlineCount] = useState(0);
30
+ const normalizedCustomerAuthority = customerAuthority?.trim();
22
31
 
23
32
  useEffect(() => {
24
- if (!authority || !customerAuthority) return;
25
-
26
- socket.emit("chat:join", { authority, customerAuthority });
33
+ if (!normalizedCustomerAuthority) return;
27
34
 
28
35
  const handleHistory = (msgs: ChatMessage[]) => {
29
36
  setMessages(msgs);
@@ -33,34 +40,54 @@ export function useChat({
33
40
  setMessages((prev) => [...prev, msg]);
34
41
  };
35
42
 
36
- const handleUsers = (data: any) => {
43
+ const handleUsers = (data: ChatUsersPayload) => {
37
44
  setOnlineCount(data.count);
38
45
  };
39
46
 
47
+ const joinChat = () => {
48
+ const joinPayload = authority
49
+ ? { authority, customerAuthority: normalizedCustomerAuthority }
50
+ : { customerAuthority: normalizedCustomerAuthority };
51
+
52
+ socket.emit("chat:join", joinPayload, (res: ChatResponse) => {
53
+ if (res?.error) {
54
+ console.error("join error", res.error);
55
+ }
56
+ });
57
+ };
58
+
40
59
  socket.on("chat:history", handleHistory);
41
60
  socket.on("chat:message", handleMessage);
42
61
  socket.on("chat:users", handleUsers);
62
+ socket.on("connect", joinChat);
63
+ joinChat();
43
64
 
44
65
  return () => {
45
- socket.emit("chat:leave", { authority, customerAuthority });
46
-
47
66
  socket.off("chat:history", handleHistory);
48
67
  socket.off("chat:message", handleMessage);
49
68
  socket.off("chat:users", handleUsers);
69
+ socket.off("connect", joinChat);
50
70
  };
51
- }, [authority, customerAuthority]);
71
+ }, [authority, normalizedCustomerAuthority]);
52
72
 
53
- const sendMessage = (message: string) => {
54
- socket.emit(
55
- "chat:message",
56
- { authority, customerAuthority, message },
57
- (res: any) => {
58
- if (res.error) {
59
- console.error("send error", res.error);
60
- }
61
- },
62
- );
63
- };
73
+ const sendMessage = (message: string): Promise<ChatResponse> =>
74
+ new Promise((resolve) => {
75
+ if (!authority || !normalizedCustomerAuthority) {
76
+ resolve({ error: "INVALID_REQUEST" });
77
+ return;
78
+ }
79
+
80
+ socket.emit(
81
+ "chat:message",
82
+ { authority, customerAuthority: normalizedCustomerAuthority, message },
83
+ (res: ChatResponse) => {
84
+ if (res?.error) {
85
+ console.error("send error", res.error);
86
+ }
87
+ resolve(res ?? { error: "INTERNAL_ERROR" });
88
+ },
89
+ );
90
+ });
64
91
 
65
92
  return {
66
93
  messages,