@triadxyz/widgets 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/LiveChat.tsx +19 -6
- package/src/hooks/useChat.ts +9 -2
package/dist/index.d.mts
CHANGED
|
@@ -46,7 +46,8 @@ interface LiveChatProps {
|
|
|
46
46
|
connectWallet: string;
|
|
47
47
|
description: string;
|
|
48
48
|
};
|
|
49
|
+
signature: string;
|
|
49
50
|
}
|
|
50
|
-
declare function LiveChat({ isOpen, onClose, title, theme, classNames, renderMessage, renderHeader, authority, customerAuthority, onConnectWallet, }: LiveChatProps): react_jsx_runtime.JSX.Element | null;
|
|
51
|
+
declare function LiveChat({ isOpen, onClose, title, theme, classNames, renderMessage, renderHeader, authority, customerAuthority, onConnectWallet, signature, }: LiveChatProps): react_jsx_runtime.JSX.Element | null;
|
|
51
52
|
|
|
52
53
|
export { ButtonWidget, type ButtonWidgetProps, LiveChat, type LiveChatClassNames, type LiveChatProps, type LiveChatTheme };
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,8 @@ interface LiveChatProps {
|
|
|
46
46
|
connectWallet: string;
|
|
47
47
|
description: string;
|
|
48
48
|
};
|
|
49
|
+
signature: string;
|
|
49
50
|
}
|
|
50
|
-
declare function LiveChat({ isOpen, onClose, title, theme, classNames, renderMessage, renderHeader, authority, customerAuthority, onConnectWallet, }: LiveChatProps): react_jsx_runtime.JSX.Element | null;
|
|
51
|
+
declare function LiveChat({ isOpen, onClose, title, theme, classNames, renderMessage, renderHeader, authority, customerAuthority, onConnectWallet, signature, }: LiveChatProps): react_jsx_runtime.JSX.Element | null;
|
|
51
52
|
|
|
52
53
|
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'),
|
|
1
|
+
'use strict';var jsxRuntime=require('react/jsx-runtime'),G=require('react'),lucideReact=require('lucide-react'),clsx=require('clsx'),tailwindMerge=require('tailwind-merge'),V=require('socket.io-client'),dateFns=require('date-fns');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var G__default=/*#__PURE__*/_interopDefault(G);var V__default=/*#__PURE__*/_interopDefault(V);var oe=({label:t,variant:g="primary",className:d="",...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"}[g]} ${d}`,...p,children:t});function y(...t){return tailwindMerge.twMerge(clsx.clsx(t))}var W=V__default.default("https://beta.triadfi.co",{path:"/socket.io",reconnection:true,reconnectionAttempts:5,reconnectionDelay:1e3,randomizationFactor:.5,timeout:5e3,transports:["websocket"]}),c=W;function E({authority:t,customerAuthority:g,signature:d}){let[p,l]=G.useState([]),[C,k]=G.useState(0),o=g?.trim();return G.useEffect(()=>{if(!o)return;let f=a=>{l(a);},h=a=>{l(m=>[...m,a]);},i=a=>{k(a.count);},b=()=>{let a=t?{authority:t,customerAuthority:o}:{customerAuthority:o};c.emit("chat:join",a,m=>{m?.error&&console.error("join error",m.error);});};return c.on("chat:history",f),c.on("chat:message",h),c.on("chat:users",i),c.on("connect",b),b(),()=>{c.off("chat:history",f),c.off("chat:message",h),c.off("chat:users",i),c.off("connect",b);}},[t,o]),{messages:p,onlineCount:C,sendMessage:f=>new Promise(h=>{if(!t||!o||!d){h({error:"INVALID_REQUEST"});return}c.emit("chat:message",{authority:t,customerAuthority:o,message:f,signature:d},i=>{i?.error&&console.error("send error",i.error),h(i??{error:"INTERNAL_ERROR"});});})}}var ee=200,te=80;function H(t){return t?[t.timestamp,t.authority??"anonymous",t.name,t.message].join(":"):null}function re(t,g=te){return t.scrollHeight-t.scrollTop-t.clientHeight<=g}function se(t){return `${t} ${t===1?"previsao":"previsoes"}`}function Me({isOpen:t,onClose:g,title:d="Live Chat",theme:p,classNames:l,renderMessage:C,renderHeader:k,authority:o,customerAuthority:L,onConnectWallet:f,signature:h}){let[i,b]=G.useState(""),a=G.useRef(null),m=G.useRef(false),M=G.useRef(true),v=G.useRef(null),P=p?.primaryColor||"#FF3D00",D=p?.background||"var(--chat-bg)",{sendMessage:B,onlineCount:_,messages:x}=E({authority:o||void 0,customerAuthority:L,signature:h}),S=()=>{let r=a.current;return {firstMessageKey:H(x[0]),lastMessageKey:H(x[x.length-1]),messageCount:x.length,scrollHeight:r?.scrollHeight??0}},K=(r="auto")=>{let e=a.current;e&&(e.scrollTo({top:e.scrollHeight,behavior:r}),M.current=true);},$=r=>{let e=a.current;if(!e)return;let s=e.scrollHeight-r.scrollHeight;s!==0&&(e.scrollTop+=s);},z=()=>{let r=a.current;r&&(M.current=re(r));};G.useLayoutEffect(()=>{if(!t){m.current=false,M.current=true,v.current=null;return}if(!a.current)return;let e=S(),s=v.current;if(!m.current){if(x.length===0){v.current=e;return}K("auto"),m.current=true,v.current=S();return}if(!s){v.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,I=e.messageCount>0&&s.messageCount>0&&e.firstMessageKey!==s.firstMessageKey&&e.lastMessageKey!==s.lastMessageKey;w?$(s):N?M.current&&K("auto"):I&&M.current&&K("auto"),v.current=S();},[t,x]);let A=()=>{let r=i.trim();if(r){if(!o){f?.();return}b(""),B(r);}},F=r=>{r.key==="Enter"&&!r.shiftKey&&(r.preventDefault(),A());};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.jsx("div",{className:"fixed inset-0 z-[110]",onClick:
|
|
8
|
+
`}),jsxRuntime.jsx("div",{className:"fixed inset-0 z-[110]",onClick:g,"aria-hidden":"true",children:jsxRuntime.jsxs("div",{onClick:r=>r.stopPropagation(),role:"dialog","aria-modal":"true","aria-label":d,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]",l?.container),style:{background:D},children:[k?k():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",l?.header),children:[jsxRuntime.jsxs("div",{className:"flex items-center gap-3",children:[jsxRuntime.jsx("h2",{className:"text-lg font-semibold text-triad-dark-100 dark:text-white",children:d}),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:[_," online"]})]})]}),jsxRuntime.jsx("button",{onClick:g,children:jsxRuntime.jsx(lucideReact.X,{className:"text-black dark:text-white",size:18})})]}),jsxRuntime.jsx("div",{ref:a,onScroll:z,className:y("flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6",l?.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),C?jsxRuntime.jsx(G__default.default.Fragment,{children:C(e)},N):jsxRuntime.jsxs("div",{className:`flex flex-col gap-1 ${e.authority===o?"items-end":"items-start"}`,children:[e.authority!==o&&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===o?"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===o?{backgroundColor:P}: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:se(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:i,onChange:r=>b(r.target.value),onKeyDown:F,maxLength:ee,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",l?.input)}),jsxRuntime.jsx("button",{onClick:A,disabled:!i.trim()||!o&&!f,className:y("flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400",l?.button),style:{opacity:i.trim()?1:.5},children:jsxRuntime.jsx(lucideReact.Send,{color:p?.buttonSend.color,size:18})})]})})})]})})]}):null}exports.ButtonWidget=oe;exports.LiveChat=Me;//# sourceMappingURL=index.js.map
|
|
9
9
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/ButtonWidget.tsx","../src/utils/cn.ts","../src/utils/socket.ts","../src/hooks/useChat.ts","../src/components/LiveChat.tsx"],"names":["ButtonWidget","label","variant","className","props","jsx","cn","classes","twMerge","clsx","socket","io","socket_default","useChat","authority","customerAuthority","messages","setMessages","useState","onlineCount","setOnlineCount","normalizedCustomerAuthority","useEffect","handleHistory","msgs","handleMessage","msg","prev","handleUsers","data","joinChat","joinPayload","res","message","resolve","CHAT_MAX_MESSAGE_LENGTH","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"]}
|
|
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","signature","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 signature,\n}: {\n authority?: string;\n customerAuthority: string;\n signature: 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 || !signature) {\n resolve({ error: \"INVALID_REQUEST\" });\n return;\n }\n\n socket.emit(\n \"chat:message\",\n {\n authority,\n customerAuthority: normalizedCustomerAuthority,\n message,\n signature,\n },\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 signature: string;\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(\n element: HTMLDivElement,\n threshold = AUTO_SCROLL_THRESHOLD_PX,\n) {\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 signature,\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 signature,\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 =\n 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\n ? \"items-end\"\n : \"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>\n {formatPredictionsCount(message.predictionsCount)}\n </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={\n !inputValue.trim() || (!authority && !onConnectWallet)\n }\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
|
|
1
|
+
import {jsx,jsxs,Fragment}from'react/jsx-runtime';import G,{useState,useRef,useLayoutEffect,useEffect}from'react';import {X,Send}from'lucide-react';import {clsx}from'clsx';import {twMerge}from'tailwind-merge';import V from'socket.io-client';import {format}from'date-fns';var oe=({label:t,variant:g="primary",className:d="",...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"}[g]} ${d}`,...p,children:t});function y(...t){return twMerge(clsx(t))}var W=V("https://beta.triadfi.co",{path:"/socket.io",reconnection:true,reconnectionAttempts:5,reconnectionDelay:1e3,randomizationFactor:.5,timeout:5e3,transports:["websocket"]}),c=W;function E({authority:t,customerAuthority:g,signature:d}){let[p,l]=useState([]),[C,k]=useState(0),o=g?.trim();return useEffect(()=>{if(!o)return;let f=a=>{l(a);},h=a=>{l(m=>[...m,a]);},i=a=>{k(a.count);},b=()=>{let a=t?{authority:t,customerAuthority:o}:{customerAuthority:o};c.emit("chat:join",a,m=>{m?.error&&console.error("join error",m.error);});};return c.on("chat:history",f),c.on("chat:message",h),c.on("chat:users",i),c.on("connect",b),b(),()=>{c.off("chat:history",f),c.off("chat:message",h),c.off("chat:users",i),c.off("connect",b);}},[t,o]),{messages:p,onlineCount:C,sendMessage:f=>new Promise(h=>{if(!t||!o||!d){h({error:"INVALID_REQUEST"});return}c.emit("chat:message",{authority:t,customerAuthority:o,message:f,signature:d},i=>{i?.error&&console.error("send error",i.error),h(i??{error:"INTERNAL_ERROR"});});})}}var ee=200,te=80;function H(t){return t?[t.timestamp,t.authority??"anonymous",t.name,t.message].join(":"):null}function re(t,g=te){return t.scrollHeight-t.scrollTop-t.clientHeight<=g}function se(t){return `${t} ${t===1?"previsao":"previsoes"}`}function Me({isOpen:t,onClose:g,title:d="Live Chat",theme:p,classNames:l,renderMessage:C,renderHeader:k,authority:o,customerAuthority:L,onConnectWallet:f,signature:h}){let[i,b]=useState(""),a=useRef(null),m=useRef(false),M=useRef(true),v=useRef(null),P=p?.primaryColor||"#FF3D00",D=p?.background||"var(--chat-bg)",{sendMessage:B,onlineCount:_,messages:x}=E({authority:o||void 0,customerAuthority:L,signature:h}),S=()=>{let r=a.current;return {firstMessageKey:H(x[0]),lastMessageKey:H(x[x.length-1]),messageCount:x.length,scrollHeight:r?.scrollHeight??0}},K=(r="auto")=>{let e=a.current;e&&(e.scrollTo({top:e.scrollHeight,behavior:r}),M.current=true);},$=r=>{let e=a.current;if(!e)return;let s=e.scrollHeight-r.scrollHeight;s!==0&&(e.scrollTop+=s);},z=()=>{let r=a.current;r&&(M.current=re(r));};useLayoutEffect(()=>{if(!t){m.current=false,M.current=true,v.current=null;return}if(!a.current)return;let e=S(),s=v.current;if(!m.current){if(x.length===0){v.current=e;return}K("auto"),m.current=true,v.current=S();return}if(!s){v.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,I=e.messageCount>0&&s.messageCount>0&&e.firstMessageKey!==s.firstMessageKey&&e.lastMessageKey!==s.lastMessageKey;w?$(s):N?M.current&&K("auto"):I&&M.current&&K("auto"),v.current=S();},[t,x]);let A=()=>{let r=i.trim();if(r){if(!o){f?.();return}b(""),B(r);}},F=r=>{r.key==="Enter"&&!r.shiftKey&&(r.preventDefault(),A());};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
|
-
`}),jsx("div",{className:"fixed inset-0 z-[110]",onClick:
|
|
8
|
+
`}),jsx("div",{className:"fixed inset-0 z-[110]",onClick:g,"aria-hidden":"true",children:jsxs("div",{onClick:r=>r.stopPropagation(),role:"dialog","aria-modal":"true","aria-label":d,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]",l?.container),style:{background:D},children:[k?k():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",l?.header),children:[jsxs("div",{className:"flex items-center gap-3",children:[jsx("h2",{className:"text-lg font-semibold text-triad-dark-100 dark:text-white",children:d}),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:[_," online"]})]})]}),jsx("button",{onClick:g,children:jsx(X,{className:"text-black dark:text-white",size:18})})]}),jsx("div",{ref:a,onScroll:z,className:y("flex-1 overflow-y-auto space-y-4 px-4 py-4 lg:px-6",l?.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),C?jsx(G.Fragment,{children:C(e)},N):jsxs("div",{className:`flex flex-col gap-1 ${e.authority===o?"items-end":"items-start"}`,children:[e.authority!==o&&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===o?"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===o?{backgroundColor:P}: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:se(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:i,onChange:r=>b(r.target.value),onKeyDown:F,maxLength:ee,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",l?.input)}),jsx("button",{onClick:A,disabled:!i.trim()||!o&&!f,className:y("flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400",l?.button),style:{opacity:i.trim()?1:.5},children:jsx(Send,{color:p?.buttonSend.color,size:18})})]})})})]})})]}):null}export{oe as ButtonWidget,Me as LiveChat};//# sourceMappingURL=index.mjs.map
|
|
9
9
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/ButtonWidget.tsx","../src/utils/cn.ts","../src/utils/socket.ts","../src/hooks/useChat.ts","../src/components/LiveChat.tsx"],"names":["ButtonWidget","label","variant","className","props","jsx","cn","classes","twMerge","clsx","socket","io","socket_default","useChat","authority","customerAuthority","messages","setMessages","useState","onlineCount","setOnlineCount","normalizedCustomerAuthority","useEffect","handleHistory","msgs","handleMessage","msg","prev","handleUsers","data","joinChat","joinPayload","res","message","resolve","CHAT_MAX_MESSAGE_LENGTH","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"]}
|
|
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","signature","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 signature,\n}: {\n authority?: string;\n customerAuthority: string;\n signature: 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 || !signature) {\n resolve({ error: \"INVALID_REQUEST\" });\n return;\n }\n\n socket.emit(\n \"chat:message\",\n {\n authority,\n customerAuthority: normalizedCustomerAuthority,\n message,\n signature,\n },\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 signature: string;\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(\n element: HTMLDivElement,\n threshold = AUTO_SCROLL_THRESHOLD_PX,\n) {\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 signature,\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 signature,\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 =\n 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\n ? \"items-end\"\n : \"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>\n {formatPredictionsCount(message.predictionsCount)}\n </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={\n !inputValue.trim() || (!authority && !onConnectWallet)\n }\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
|
@@ -12,7 +12,7 @@ export interface LiveChatTheme {
|
|
|
12
12
|
textColor?: string;
|
|
13
13
|
buttonSend: {
|
|
14
14
|
color: string;
|
|
15
|
-
}
|
|
15
|
+
};
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export interface LiveChatClassNames {
|
|
@@ -44,6 +44,7 @@ export interface LiveChatProps {
|
|
|
44
44
|
connectWallet: string;
|
|
45
45
|
description: string;
|
|
46
46
|
};
|
|
47
|
+
signature: string;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
const CHAT_MAX_MESSAGE_LENGTH = 200;
|
|
@@ -67,7 +68,10 @@ function getMessageIdentity(message?: ChatMessage) {
|
|
|
67
68
|
].join(":");
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
function isNearBottom(
|
|
71
|
+
function isNearBottom(
|
|
72
|
+
element: HTMLDivElement,
|
|
73
|
+
threshold = AUTO_SCROLL_THRESHOLD_PX,
|
|
74
|
+
) {
|
|
71
75
|
const distanceToBottom =
|
|
72
76
|
element.scrollHeight - element.scrollTop - element.clientHeight;
|
|
73
77
|
|
|
@@ -89,6 +93,7 @@ export function LiveChat({
|
|
|
89
93
|
authority,
|
|
90
94
|
customerAuthority,
|
|
91
95
|
onConnectWallet,
|
|
96
|
+
signature,
|
|
92
97
|
}: LiveChatProps) {
|
|
93
98
|
const [inputValue, setInputValue] = useState("");
|
|
94
99
|
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
|
@@ -101,6 +106,7 @@ export function LiveChat({
|
|
|
101
106
|
const { sendMessage, onlineCount, messages } = useChat({
|
|
102
107
|
authority: authority || undefined,
|
|
103
108
|
customerAuthority: customerAuthority,
|
|
109
|
+
signature,
|
|
104
110
|
});
|
|
105
111
|
|
|
106
112
|
const createMessagesSnapshot = (): MessagesSnapshot => {
|
|
@@ -132,7 +138,8 @@ export function LiveChat({
|
|
|
132
138
|
const container = messagesContainerRef.current;
|
|
133
139
|
if (!container) return;
|
|
134
140
|
|
|
135
|
-
const scrollHeightDelta =
|
|
141
|
+
const scrollHeightDelta =
|
|
142
|
+
container.scrollHeight - previousSnapshot.scrollHeight;
|
|
136
143
|
|
|
137
144
|
if (scrollHeightDelta !== 0) {
|
|
138
145
|
container.scrollTop += scrollHeightDelta;
|
|
@@ -311,7 +318,9 @@ export function LiveChat({
|
|
|
311
318
|
<div
|
|
312
319
|
key={messageKey}
|
|
313
320
|
className={`flex flex-col gap-1 ${
|
|
314
|
-
message.authority === authority
|
|
321
|
+
message.authority === authority
|
|
322
|
+
? "items-end"
|
|
323
|
+
: "items-start"
|
|
315
324
|
}`}
|
|
316
325
|
>
|
|
317
326
|
{message.authority !== authority && (
|
|
@@ -337,7 +346,9 @@ export function LiveChat({
|
|
|
337
346
|
</div>
|
|
338
347
|
|
|
339
348
|
<div className="flex items-center gap-2 text-[10px] text-black opacity-50 dark:text-white">
|
|
340
|
-
<span>
|
|
349
|
+
<span>
|
|
350
|
+
{formatPredictionsCount(message.predictionsCount)}
|
|
351
|
+
</span>
|
|
341
352
|
<span>{format(message.timestamp, "HH:mm")}</span>
|
|
342
353
|
</div>
|
|
343
354
|
</div>
|
|
@@ -363,7 +374,9 @@ export function LiveChat({
|
|
|
363
374
|
|
|
364
375
|
<button
|
|
365
376
|
onClick={handleSendMessage}
|
|
366
|
-
disabled={
|
|
377
|
+
disabled={
|
|
378
|
+
!inputValue.trim() || (!authority && !onConnectWallet)
|
|
379
|
+
}
|
|
367
380
|
className={cn(
|
|
368
381
|
"flex size-10 items-center justify-center rounded-xl transition-colors bg-app-dark-400",
|
|
369
382
|
classNames?.button,
|
package/src/hooks/useChat.ts
CHANGED
|
@@ -21,9 +21,11 @@ interface ChatResponse {
|
|
|
21
21
|
export function useChat({
|
|
22
22
|
authority,
|
|
23
23
|
customerAuthority,
|
|
24
|
+
signature,
|
|
24
25
|
}: {
|
|
25
26
|
authority?: string;
|
|
26
27
|
customerAuthority: string;
|
|
28
|
+
signature: string;
|
|
27
29
|
}) {
|
|
28
30
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
29
31
|
const [onlineCount, setOnlineCount] = useState(0);
|
|
@@ -72,14 +74,19 @@ export function useChat({
|
|
|
72
74
|
|
|
73
75
|
const sendMessage = (message: string): Promise<ChatResponse> =>
|
|
74
76
|
new Promise((resolve) => {
|
|
75
|
-
if (!authority || !normalizedCustomerAuthority) {
|
|
77
|
+
if (!authority || !normalizedCustomerAuthority || !signature) {
|
|
76
78
|
resolve({ error: "INVALID_REQUEST" });
|
|
77
79
|
return;
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
socket.emit(
|
|
81
83
|
"chat:message",
|
|
82
|
-
{
|
|
84
|
+
{
|
|
85
|
+
authority,
|
|
86
|
+
customerAuthority: normalizedCustomerAuthority,
|
|
87
|
+
message,
|
|
88
|
+
signature,
|
|
89
|
+
},
|
|
83
90
|
(res: ChatResponse) => {
|
|
84
91
|
if (res?.error) {
|
|
85
92
|
console.error("send error", res.error);
|