@triadxyz/widgets 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.d.mts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +9 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -0
- package/src/components/ButtonWidget.tsx +30 -0
- package/src/components/LiveChat.tsx +260 -0
- package/src/hooks/useChat.ts +75 -0
- package/src/index.ts +2 -0
- package/src/utils/cn.ts +7 -0
- package/src/utils/socket.ts +13 -0
- package/tsconfig.json +22 -0
- package/tsup.config.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# triadmarkets-widgets
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
interface ButtonWidgetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
5
|
+
label: string;
|
|
6
|
+
variant?: 'primary' | 'secondary';
|
|
7
|
+
}
|
|
8
|
+
declare const ButtonWidget: React.FC<ButtonWidgetProps>;
|
|
9
|
+
|
|
10
|
+
interface ChatMessage {
|
|
11
|
+
authority: string;
|
|
12
|
+
name: string;
|
|
13
|
+
image: string;
|
|
14
|
+
predictionsCount: number;
|
|
15
|
+
message: string;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface LiveChatTheme {
|
|
20
|
+
primaryColor?: string;
|
|
21
|
+
background?: string;
|
|
22
|
+
textColor?: string;
|
|
23
|
+
}
|
|
24
|
+
interface LiveChatClassNames {
|
|
25
|
+
container?: string;
|
|
26
|
+
header?: string;
|
|
27
|
+
messages?: string;
|
|
28
|
+
input?: string;
|
|
29
|
+
button?: string;
|
|
30
|
+
}
|
|
31
|
+
interface LiveChatProps {
|
|
32
|
+
isOpen: boolean;
|
|
33
|
+
onClose: () => void;
|
|
34
|
+
title?: string;
|
|
35
|
+
theme?: LiveChatTheme;
|
|
36
|
+
classNames?: LiveChatClassNames;
|
|
37
|
+
renderMessage?: (message: ChatMessage) => React.ReactNode;
|
|
38
|
+
renderHeader?: () => React.ReactNode;
|
|
39
|
+
authority: string;
|
|
40
|
+
customerAuthority: string;
|
|
41
|
+
}
|
|
42
|
+
declare function LiveChat({ isOpen, onClose, title, theme, classNames, renderMessage, renderHeader, authority, customerAuthority, }: LiveChatProps): react_jsx_runtime.JSX.Element | null;
|
|
43
|
+
|
|
44
|
+
export { ButtonWidget, type ButtonWidgetProps, LiveChat, type LiveChatClassNames, type LiveChatProps, type LiveChatTheme };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
interface ButtonWidgetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
5
|
+
label: string;
|
|
6
|
+
variant?: 'primary' | 'secondary';
|
|
7
|
+
}
|
|
8
|
+
declare const ButtonWidget: React.FC<ButtonWidgetProps>;
|
|
9
|
+
|
|
10
|
+
interface ChatMessage {
|
|
11
|
+
authority: string;
|
|
12
|
+
name: string;
|
|
13
|
+
image: string;
|
|
14
|
+
predictionsCount: number;
|
|
15
|
+
message: string;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface LiveChatTheme {
|
|
20
|
+
primaryColor?: string;
|
|
21
|
+
background?: string;
|
|
22
|
+
textColor?: string;
|
|
23
|
+
}
|
|
24
|
+
interface LiveChatClassNames {
|
|
25
|
+
container?: string;
|
|
26
|
+
header?: string;
|
|
27
|
+
messages?: string;
|
|
28
|
+
input?: string;
|
|
29
|
+
button?: string;
|
|
30
|
+
}
|
|
31
|
+
interface LiveChatProps {
|
|
32
|
+
isOpen: boolean;
|
|
33
|
+
onClose: () => void;
|
|
34
|
+
title?: string;
|
|
35
|
+
theme?: LiveChatTheme;
|
|
36
|
+
classNames?: LiveChatClassNames;
|
|
37
|
+
renderMessage?: (message: ChatMessage) => React.ReactNode;
|
|
38
|
+
renderHeader?: () => React.ReactNode;
|
|
39
|
+
authority: string;
|
|
40
|
+
customerAuthority: string;
|
|
41
|
+
}
|
|
42
|
+
declare function LiveChat({ isOpen, onClose, title, theme, classNames, renderMessage, renderHeader, authority, customerAuthority, }: LiveChatProps): react_jsx_runtime.JSX.Element | null;
|
|
43
|
+
|
|
44
|
+
export { ButtonWidget, type ButtonWidgetProps, LiveChat, type LiveChatClassNames, type LiveChatProps, type LiveChatTheme };
|
package/dist/index.js
ADDED
|
@@ -0,0 +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 W=({label:s,variant:n="primary",className:p="",...m})=>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"}[n]} ${p}`,...m,children:s});function d(...s){return tailwindMerge.twMerge(clsx.clsx(s))}var T=S__default.default("https://beta.triadfi.co",{path:"/socket.io",reconnection:true,reconnectionAttempts:5,reconnectionDelay:1e3,randomizationFactor:.5,timeout:5e3,transports:["websocket"]}),a=T;function w({authority:s,customerAuthority:n}){let[p,m]=react.useState([]),[i,u]=react.useState(0);return react.useEffect(()=>{if(!(!n))return a.connected||a.connect(),a.emit("chat:join",{authority:s,customerAuthority:n},r=>{r.error&&console.error("join error",r.error);}),a.off("chat:history"),a.on("chat:history",r=>{m(c=>c.length?c:r);}),a.off("chat:message"),a.on("chat:message",r=>{m(c=>[...c,r]);}),a.off("chat:users"),a.on("chat:users",r=>{u(r.count);}),()=>{a.off("chat:history"),a.off("chat:message"),a.off("chat:users");}},[s,n]),{messages:p,onlineCount:i,sendMessage:r=>{a.emit("chat:message",{authority:s,customerAuthority:n,message:r},c=>{c.error&&console.error("send error",c.error);});}}}function se({isOpen:s,onClose:n,title:p="Live Chat",theme:m,classNames:i,renderMessage:u,renderHeader:f,authority:r,customerAuthority:c}){let[l,g]=react.useState(""),x=react.useRef(null),h=m?.primaryColor||"#FF3D00",L=m?.background||"var(--chat-bg)",{sendMessage:R,onlineCount:D,messages:b}=w({authority:r||"random",customerAuthority:c}),z=()=>{x.current?.scrollIntoView({behavior:"smooth"});};react.useEffect(()=>{z();},[b]);let v=()=>{l.trim()&&(g(""),R(l));},k=t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),v());};return s?jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx("style",{children:`
|
|
2
|
+
:root {
|
|
3
|
+
--chat-bg: #ffffff;
|
|
4
|
+
}
|
|
5
|
+
.dark {
|
|
6
|
+
--chat-bg: #15181D;
|
|
7
|
+
}
|
|
8
|
+
`}),jsxRuntime.jsxs("div",{onClick:t=>t.stopPropagation(),className:d("fixed right-6 z-[110] hidden lg:flex flex-col overflow-hidden rounded-3xl shadow-2xl",i?.container),style:{top:"75px",width:"340px",height:"800px",maxHeight:"calc(100vh - 90px)",background:L},children:[f?f():jsxRuntime.jsxs("div",{className:d("flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10",i?.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:p}),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-black/50 dark:text-white/50",children:[D," online"]})]})]}),jsxRuntime.jsx("button",{onClick:n,children:jsxRuntime.jsx(lucideReact.X,{size:18})})]}),jsxRuntime.jsxs("div",{className:d("flex-1 overflow-y-auto px-6 py-4 space-y-4",i?.messages),children:[b.map((t,y)=>u?u(t):jsxRuntime.jsxs("div",{className:`flex flex-col gap-1 ${t.authority===r?"items-end":"items-start"}`,children:[t.authority!==r&&jsxRuntime.jsx("span",{className:"text-xs font-medium text-triad-dark-100 dark:text-white",children:t.name}),jsxRuntime.jsx("div",{className:d("max-w-[75%] rounded-2xl px-4 py-2.5",t.authority===r?"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===r?{backgroundColor:h}:void 0,children:jsxRuntime.jsx("p",{className:"text-sm break-words",children:t.message})}),jsxRuntime.jsx("span",{className:"text-[10px] opacity-50",children:dateFns.format(t.timestamp,"HH:mm")})]},y)),jsxRuntime.jsx("div",{ref:x})]}),jsxRuntime.jsx("div",{className:"px-6 py-4 border-t border-black/10 dark:border-white/10",children:jsxRuntime.jsxs("div",{className:"flex items-center gap-2",children:[jsxRuntime.jsx("input",{value:l,onChange:t=>g(t.target.value),onKeyDown:k,placeholder:"Digite sua mensagem...",className:d("flex-1 px-4 py-2.5 rounded-xl bg-black/5 dark:bg-white/5 dark:text-white text-sm outline-none",i?.input)}),jsxRuntime.jsx("button",{onClick:v,disabled:!l.trim(),className:d("flex items-center justify-center size-10 rounded-xl transition-colors",i?.button),style:{backgroundColor:l.trim()?h:void 0,opacity:l.trim()?1:.5},children:jsxRuntime.jsx(lucideReact.Send,{size:18,className:"dark:text-white text-app-gay-400"})})]})})]}),jsxRuntime.jsx("div",{className:"fixed inset-0 z-[110] flex flex-col lg:hidden bg-white dark:bg-[#15181D]",children:jsxRuntime.jsxs("div",{className:"flex-1 flex flex-col",children:[jsxRuntime.jsxs("div",{className:"flex items-center justify-between px-4 py-4 border-b",children:[jsxRuntime.jsx("h2",{children:p}),jsxRuntime.jsx("button",{onClick:n,children:jsxRuntime.jsx(lucideReact.X,{})})]}),jsxRuntime.jsxs("div",{className:"flex-1 overflow-y-auto px-4 py-4 space-y-4",children:[b.map((t,y)=>jsxRuntime.jsx("div",{children:t.message},y)),jsxRuntime.jsx("div",{ref:x})]}),jsxRuntime.jsxs("div",{className:"p-4 border-t flex gap-2",children:[jsxRuntime.jsx("input",{value:l,onChange:t=>g(t.target.value),onKeyDown:k,className:"flex-1"}),jsxRuntime.jsx("button",{className:d(i?.button),style:{backgroundColor:l.trim()?h:void 0,opacity:l.trim()?1:.5},onClick:v,children:jsxRuntime.jsx(lucideReact.Send,{className:"dark:text-white text-app-gay-400"})})]})]})})]}):null}exports.ButtonWidget=W;exports.LiveChat=se;//# sourceMappingURL=index.js.map
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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","res","msgs","prev","msg","data","message","LiveChat","isOpen","onClose","title","theme","classNames","renderMessage","renderHeader","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 if (!socket.connected) {\n socket.connect();\n }\n\n socket.emit(\"chat:join\", { authority, customerAuthority }, (res: any) => {\n if (res.error) {\n console.error(\"join error\", res.error);\n }\n });\n\n socket.off(\"chat:history\");\n socket.on(\"chat:history\", (msgs) => {\n setMessages((prev) => (prev.length ? prev : msgs));\n });\n\n socket.off(\"chat:message\");\n socket.on(\"chat:message\", (msg) => {\n setMessages((prev) => [...prev, msg]);\n });\n\n socket.off(\"chat:users\");\n socket.on(\"chat:users\", (data) => {\n setOnlineCount(data.count);\n });\n\n return () => {\n socket.off(\"chat:history\");\n socket.off(\"chat:message\");\n socket.off(\"chat:users\");\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;\n customerAuthority: string;\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}: 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 const { sendMessage, onlineCount, messages } = useChat({\n authority: authority || \"random\",\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 rounded-3xl shadow-2xl\",\n classNames?.container,\n )}\n style={{\n top: \"75px\",\n width: \"340px\",\n height: \"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-black/50 dark:text-white/50\">\n {onlineCount} online\n </span>\n </div>\n </div>\n <button onClick={onClose}>\n <X size={18} />\n </button>\n </div>\n )}\n\n {/* Messages */}\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 ? \"items-end\" : \"items-start\"\n }`}\n >\n {message.authority !== authority && (\n <span className=\"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 ? \"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\">\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 <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 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 </div>\n </div>\n\n {/* Mobile */}\n <div className=\"fixed inset-0 z-[110] flex flex-col lg:hidden bg-white dark:bg-[#15181D]\">\n {/* Reaproveita mesma estrutura */}\n <div className=\"flex-1 flex flex-col\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-4 border-b\">\n <h2>{title}</h2>\n <button onClick={onClose}>\n <X />\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 gap-2\">\n <input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n className=\"flex-1\"\n />\n <button\n className={cn(classNames?.button)}\n style={{\n backgroundColor: inputValue.trim() ? primary : undefined,\n opacity: inputValue.trim() ? 1 : 0.5,\n }}\n onClick={handleSendMessage}\n >\n <Send className=\"dark:text-white text-app-gay-400\" />\n </button>\n </div>\n </div>\n </div>\n </>\n );\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +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 W=({label:s,variant:n="primary",className:p="",...m})=>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"}[n]} ${p}`,...m,children:s});function d(...s){return twMerge(clsx(s))}var T=S("https://beta.triadfi.co",{path:"/socket.io",reconnection:true,reconnectionAttempts:5,reconnectionDelay:1e3,randomizationFactor:.5,timeout:5e3,transports:["websocket"]}),a=T;function w({authority:s,customerAuthority:n}){let[p,m]=useState([]),[i,u]=useState(0);return useEffect(()=>{if(!(!n))return a.connected||a.connect(),a.emit("chat:join",{authority:s,customerAuthority:n},r=>{r.error&&console.error("join error",r.error);}),a.off("chat:history"),a.on("chat:history",r=>{m(c=>c.length?c:r);}),a.off("chat:message"),a.on("chat:message",r=>{m(c=>[...c,r]);}),a.off("chat:users"),a.on("chat:users",r=>{u(r.count);}),()=>{a.off("chat:history"),a.off("chat:message"),a.off("chat:users");}},[s,n]),{messages:p,onlineCount:i,sendMessage:r=>{a.emit("chat:message",{authority:s,customerAuthority:n,message:r},c=>{c.error&&console.error("send error",c.error);});}}}function se({isOpen:s,onClose:n,title:p="Live Chat",theme:m,classNames:i,renderMessage:u,renderHeader:f,authority:r,customerAuthority:c}){let[l,g]=useState(""),x=useRef(null),h=m?.primaryColor||"#FF3D00",L=m?.background||"var(--chat-bg)",{sendMessage:R,onlineCount:D,messages:b}=w({authority:r||"random",customerAuthority:c}),z=()=>{x.current?.scrollIntoView({behavior:"smooth"});};useEffect(()=>{z();},[b]);let v=()=>{l.trim()&&(g(""),R(l));},k=t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),v());};return s?jsxs(Fragment,{children:[jsx("style",{children:`
|
|
2
|
+
:root {
|
|
3
|
+
--chat-bg: #ffffff;
|
|
4
|
+
}
|
|
5
|
+
.dark {
|
|
6
|
+
--chat-bg: #15181D;
|
|
7
|
+
}
|
|
8
|
+
`}),jsxs("div",{onClick:t=>t.stopPropagation(),className:d("fixed right-6 z-[110] hidden lg:flex flex-col overflow-hidden rounded-3xl shadow-2xl",i?.container),style:{top:"75px",width:"340px",height:"800px",maxHeight:"calc(100vh - 90px)",background:L},children:[f?f():jsxs("div",{className:d("flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10",i?.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:p}),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-black/50 dark:text-white/50",children:[D," online"]})]})]}),jsx("button",{onClick:n,children:jsx(X,{size:18})})]}),jsxs("div",{className:d("flex-1 overflow-y-auto px-6 py-4 space-y-4",i?.messages),children:[b.map((t,y)=>u?u(t):jsxs("div",{className:`flex flex-col gap-1 ${t.authority===r?"items-end":"items-start"}`,children:[t.authority!==r&&jsx("span",{className:"text-xs font-medium text-triad-dark-100 dark:text-white",children:t.name}),jsx("div",{className:d("max-w-[75%] rounded-2xl px-4 py-2.5",t.authority===r?"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===r?{backgroundColor:h}:void 0,children:jsx("p",{className:"text-sm break-words",children:t.message})}),jsx("span",{className:"text-[10px] opacity-50",children:format(t.timestamp,"HH:mm")})]},y)),jsx("div",{ref:x})]}),jsx("div",{className:"px-6 py-4 border-t border-black/10 dark:border-white/10",children:jsxs("div",{className:"flex items-center gap-2",children:[jsx("input",{value:l,onChange:t=>g(t.target.value),onKeyDown:k,placeholder:"Digite sua mensagem...",className:d("flex-1 px-4 py-2.5 rounded-xl bg-black/5 dark:bg-white/5 dark:text-white text-sm outline-none",i?.input)}),jsx("button",{onClick:v,disabled:!l.trim(),className:d("flex items-center justify-center size-10 rounded-xl transition-colors",i?.button),style:{backgroundColor:l.trim()?h:void 0,opacity:l.trim()?1:.5},children:jsx(Send,{size:18,className:"dark:text-white text-app-gay-400"})})]})})]}),jsx("div",{className:"fixed inset-0 z-[110] flex flex-col lg:hidden bg-white dark:bg-[#15181D]",children:jsxs("div",{className:"flex-1 flex flex-col",children:[jsxs("div",{className:"flex items-center justify-between px-4 py-4 border-b",children:[jsx("h2",{children:p}),jsx("button",{onClick:n,children:jsx(X,{})})]}),jsxs("div",{className:"flex-1 overflow-y-auto px-4 py-4 space-y-4",children:[b.map((t,y)=>jsx("div",{children:t.message},y)),jsx("div",{ref:x})]}),jsxs("div",{className:"p-4 border-t flex gap-2",children:[jsx("input",{value:l,onChange:t=>g(t.target.value),onKeyDown:k,className:"flex-1"}),jsx("button",{className:d(i?.button),style:{backgroundColor:l.trim()?h:void 0,opacity:l.trim()?1:.5},onClick:v,children:jsx(Send,{className:"dark:text-white text-app-gay-400"})})]})]})})]}):null}export{W as ButtonWidget,se as LiveChat};//# sourceMappingURL=index.mjs.map
|
|
9
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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","res","msgs","prev","msg","data","message","LiveChat","isOpen","onClose","title","theme","classNames","renderMessage","renderHeader","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 if (!socket.connected) {\n socket.connect();\n }\n\n socket.emit(\"chat:join\", { authority, customerAuthority }, (res: any) => {\n if (res.error) {\n console.error(\"join error\", res.error);\n }\n });\n\n socket.off(\"chat:history\");\n socket.on(\"chat:history\", (msgs) => {\n setMessages((prev) => (prev.length ? prev : msgs));\n });\n\n socket.off(\"chat:message\");\n socket.on(\"chat:message\", (msg) => {\n setMessages((prev) => [...prev, msg]);\n });\n\n socket.off(\"chat:users\");\n socket.on(\"chat:users\", (data) => {\n setOnlineCount(data.count);\n });\n\n return () => {\n socket.off(\"chat:history\");\n socket.off(\"chat:message\");\n socket.off(\"chat:users\");\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;\n customerAuthority: string;\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}: 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 const { sendMessage, onlineCount, messages } = useChat({\n authority: authority || \"random\",\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 rounded-3xl shadow-2xl\",\n classNames?.container,\n )}\n style={{\n top: \"75px\",\n width: \"340px\",\n height: \"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-black/50 dark:text-white/50\">\n {onlineCount} online\n </span>\n </div>\n </div>\n <button onClick={onClose}>\n <X size={18} />\n </button>\n </div>\n )}\n\n {/* Messages */}\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 ? \"items-end\" : \"items-start\"\n }`}\n >\n {message.authority !== authority && (\n <span className=\"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 ? \"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\">\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 <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 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 </div>\n </div>\n\n {/* Mobile */}\n <div className=\"fixed inset-0 z-[110] flex flex-col lg:hidden bg-white dark:bg-[#15181D]\">\n {/* Reaproveita mesma estrutura */}\n <div className=\"flex-1 flex flex-col\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-4 border-b\">\n <h2>{title}</h2>\n <button onClick={onClose}>\n <X />\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 gap-2\">\n <input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n className=\"flex-1\"\n />\n <button\n className={cn(classNames?.button)}\n style={{\n backgroundColor: inputValue.trim() ? primary : undefined,\n opacity: inputValue.trim() ? 1 : 0.5,\n }}\n onClick={handleSendMessage}\n >\n <Send className=\"dark:text-white text-app-gay-400\" />\n </button>\n </div>\n </div>\n </div>\n </>\n );\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@triadxyz/widgets",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Shared widgets and logic for Triad Next.js whitelabels",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"lint": "eslint src"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"next": "^16.1.7",
|
|
26
|
+
"react": "^19.2.4",
|
|
27
|
+
"react-dom": "^19.2.4"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"clsx": "^2.1.1",
|
|
31
|
+
"date-fns": "^4.1.0",
|
|
32
|
+
"lucide-react": "^0.476.0",
|
|
33
|
+
"socket.io-client": "^4.8.3",
|
|
34
|
+
"tailwind-merge": "^3.5.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/react": "^18.2.0",
|
|
38
|
+
"@types/react-dom": "^18.2.0",
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
export interface ButtonWidgetProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
6
|
+
label: string;
|
|
7
|
+
variant?: 'primary' | 'secondary';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const ButtonWidget: React.FC<ButtonWidgetProps> = ({
|
|
11
|
+
label,
|
|
12
|
+
variant = 'primary',
|
|
13
|
+
className = '',
|
|
14
|
+
...props
|
|
15
|
+
}) => {
|
|
16
|
+
const baseStyles = 'px-4 py-2 rounded-lg font-medium transition-colors';
|
|
17
|
+
const variants = {
|
|
18
|
+
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
|
19
|
+
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<button
|
|
24
|
+
className={`${baseStyles} ${variants[variant]} ${className}`}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
{label}
|
|
28
|
+
</button>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
4
|
+
import { X, Send } from "lucide-react";
|
|
5
|
+
import { cn } from "@/utils/cn";
|
|
6
|
+
import { useChat, ChatMessage } from "@/hooks/useChat";
|
|
7
|
+
import { format } from "date-fns";
|
|
8
|
+
|
|
9
|
+
export interface LiveChatTheme {
|
|
10
|
+
primaryColor?: string;
|
|
11
|
+
background?: string;
|
|
12
|
+
textColor?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LiveChatClassNames {
|
|
16
|
+
container?: string;
|
|
17
|
+
header?: string;
|
|
18
|
+
messages?: string;
|
|
19
|
+
input?: string;
|
|
20
|
+
button?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LiveChatProps {
|
|
24
|
+
isOpen: boolean;
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
|
|
27
|
+
// ui
|
|
28
|
+
title?: string;
|
|
29
|
+
|
|
30
|
+
// customization
|
|
31
|
+
theme?: LiveChatTheme;
|
|
32
|
+
classNames?: LiveChatClassNames;
|
|
33
|
+
|
|
34
|
+
// slots
|
|
35
|
+
renderMessage?: (message: ChatMessage) => React.ReactNode;
|
|
36
|
+
renderHeader?: () => React.ReactNode;
|
|
37
|
+
authority: string;
|
|
38
|
+
customerAuthority: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function LiveChat({
|
|
42
|
+
isOpen,
|
|
43
|
+
onClose,
|
|
44
|
+
title = "Live Chat",
|
|
45
|
+
theme,
|
|
46
|
+
classNames,
|
|
47
|
+
renderMessage,
|
|
48
|
+
renderHeader,
|
|
49
|
+
authority,
|
|
50
|
+
customerAuthority,
|
|
51
|
+
}: LiveChatProps) {
|
|
52
|
+
const [inputValue, setInputValue] = useState("");
|
|
53
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
54
|
+
const primary = theme?.primaryColor || "#FF3D00";
|
|
55
|
+
const background = theme?.background || "var(--chat-bg)";
|
|
56
|
+
const { sendMessage, onlineCount, messages } = useChat({
|
|
57
|
+
authority: authority || "random",
|
|
58
|
+
customerAuthority: customerAuthority,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const scrollToBottom = () => {
|
|
62
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
scrollToBottom();
|
|
67
|
+
}, [messages]);
|
|
68
|
+
|
|
69
|
+
const handleSendMessage = () => {
|
|
70
|
+
if (!inputValue.trim()) return;
|
|
71
|
+
setInputValue("");
|
|
72
|
+
sendMessage(inputValue);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
76
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
handleSendMessage();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if (!isOpen) return null;
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
<style>{`
|
|
87
|
+
:root {
|
|
88
|
+
--chat-bg: #ffffff;
|
|
89
|
+
}
|
|
90
|
+
.dark {
|
|
91
|
+
--chat-bg: #15181D;
|
|
92
|
+
}
|
|
93
|
+
`}</style>
|
|
94
|
+
|
|
95
|
+
{/* Desktop */}
|
|
96
|
+
<div
|
|
97
|
+
onClick={(e) => e.stopPropagation()}
|
|
98
|
+
className={cn(
|
|
99
|
+
"fixed right-6 z-[110] hidden lg:flex flex-col overflow-hidden rounded-3xl shadow-2xl",
|
|
100
|
+
classNames?.container,
|
|
101
|
+
)}
|
|
102
|
+
style={{
|
|
103
|
+
top: "75px",
|
|
104
|
+
width: "340px",
|
|
105
|
+
height: "800px",
|
|
106
|
+
maxHeight: "calc(100vh - 90px)",
|
|
107
|
+
background,
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
{/* Header */}
|
|
111
|
+
{renderHeader ? (
|
|
112
|
+
renderHeader()
|
|
113
|
+
) : (
|
|
114
|
+
<div
|
|
115
|
+
className={cn(
|
|
116
|
+
"flex items-center justify-between px-6 py-4 border-b border-black/10 dark:border-white/10",
|
|
117
|
+
classNames?.header,
|
|
118
|
+
)}
|
|
119
|
+
>
|
|
120
|
+
<div className="flex items-center gap-3">
|
|
121
|
+
<h2 className="text-lg font-semibold text-triad-dark-100 dark:text-white">
|
|
122
|
+
{title}
|
|
123
|
+
</h2>
|
|
124
|
+
<div className="flex items-center gap-1.5">
|
|
125
|
+
<div className="size-2 rounded-full bg-green-500 animate-pulse" />
|
|
126
|
+
<span className="text-xs text-black/50 dark:text-white/50">
|
|
127
|
+
{onlineCount} online
|
|
128
|
+
</span>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
<button onClick={onClose}>
|
|
132
|
+
<X size={18} />
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{/* Messages */}
|
|
138
|
+
<div
|
|
139
|
+
className={cn(
|
|
140
|
+
"flex-1 overflow-y-auto px-6 py-4 space-y-4",
|
|
141
|
+
classNames?.messages,
|
|
142
|
+
)}
|
|
143
|
+
>
|
|
144
|
+
{messages.map((message, idx) =>
|
|
145
|
+
renderMessage ? (
|
|
146
|
+
renderMessage(message)
|
|
147
|
+
) : (
|
|
148
|
+
<div
|
|
149
|
+
key={idx}
|
|
150
|
+
className={`flex flex-col gap-1 ${
|
|
151
|
+
message.authority === authority ? "items-end" : "items-start"
|
|
152
|
+
}`}
|
|
153
|
+
>
|
|
154
|
+
{message.authority !== authority && (
|
|
155
|
+
<span className="text-xs font-medium text-triad-dark-100 dark:text-white">
|
|
156
|
+
{message.name}
|
|
157
|
+
</span>
|
|
158
|
+
)}
|
|
159
|
+
|
|
160
|
+
<div
|
|
161
|
+
className={cn(
|
|
162
|
+
"max-w-[75%] rounded-2xl px-4 py-2.5",
|
|
163
|
+
message.authority === authority
|
|
164
|
+
? "text-white rounded-br-sm"
|
|
165
|
+
: "bg-black/5 dark:bg-white/5 text-triad-dark-100 dark:text-white rounded-bl-sm",
|
|
166
|
+
)}
|
|
167
|
+
style={
|
|
168
|
+
message.authority === authority
|
|
169
|
+
? { backgroundColor: primary }
|
|
170
|
+
: undefined
|
|
171
|
+
}
|
|
172
|
+
>
|
|
173
|
+
<p className="text-sm break-words">{message.message}</p>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<span className="text-[10px] opacity-50">
|
|
177
|
+
{format(message.timestamp, "HH:mm")}
|
|
178
|
+
</span>
|
|
179
|
+
</div>
|
|
180
|
+
),
|
|
181
|
+
)}
|
|
182
|
+
<div ref={messagesEndRef} />
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Input */}
|
|
186
|
+
<div className="px-6 py-4 border-t border-black/10 dark:border-white/10">
|
|
187
|
+
<div className="flex items-center gap-2">
|
|
188
|
+
<input
|
|
189
|
+
value={inputValue}
|
|
190
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
191
|
+
onKeyDown={handleKeyDown}
|
|
192
|
+
placeholder="Digite sua mensagem..."
|
|
193
|
+
className={cn(
|
|
194
|
+
"flex-1 px-4 py-2.5 rounded-xl bg-black/5 dark:bg-white/5 dark:text-white text-sm outline-none",
|
|
195
|
+
classNames?.input,
|
|
196
|
+
)}
|
|
197
|
+
/>
|
|
198
|
+
|
|
199
|
+
<button
|
|
200
|
+
onClick={handleSendMessage}
|
|
201
|
+
disabled={!inputValue.trim()}
|
|
202
|
+
className={cn(
|
|
203
|
+
"flex items-center justify-center size-10 rounded-xl transition-colors",
|
|
204
|
+
classNames?.button,
|
|
205
|
+
)}
|
|
206
|
+
style={{
|
|
207
|
+
backgroundColor: inputValue.trim() ? primary : undefined,
|
|
208
|
+
opacity: inputValue.trim() ? 1 : 0.5,
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
<Send size={18} className="dark:text-white text-app-gay-400" />
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
{/* Mobile */}
|
|
218
|
+
<div className="fixed inset-0 z-[110] flex flex-col lg:hidden bg-white dark:bg-[#15181D]">
|
|
219
|
+
{/* Reaproveita mesma estrutura */}
|
|
220
|
+
<div className="flex-1 flex flex-col">
|
|
221
|
+
{/* Header */}
|
|
222
|
+
<div className="flex items-center justify-between px-4 py-4 border-b">
|
|
223
|
+
<h2>{title}</h2>
|
|
224
|
+
<button onClick={onClose}>
|
|
225
|
+
<X />
|
|
226
|
+
</button>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
{/* Messages */}
|
|
230
|
+
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-4">
|
|
231
|
+
{messages.map((m, idx) => (
|
|
232
|
+
<div key={idx}>{m.message}</div>
|
|
233
|
+
))}
|
|
234
|
+
<div ref={messagesEndRef} />
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
{/* Input */}
|
|
238
|
+
<div className="p-4 border-t flex gap-2">
|
|
239
|
+
<input
|
|
240
|
+
value={inputValue}
|
|
241
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
242
|
+
onKeyDown={handleKeyDown}
|
|
243
|
+
className="flex-1"
|
|
244
|
+
/>
|
|
245
|
+
<button
|
|
246
|
+
className={cn(classNames?.button)}
|
|
247
|
+
style={{
|
|
248
|
+
backgroundColor: inputValue.trim() ? primary : undefined,
|
|
249
|
+
opacity: inputValue.trim() ? 1 : 0.5,
|
|
250
|
+
}}
|
|
251
|
+
onClick={handleSendMessage}
|
|
252
|
+
>
|
|
253
|
+
<Send className="dark:text-white text-app-gay-400" />
|
|
254
|
+
</button>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import socket from "@/utils/socket";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
export interface ChatMessage {
|
|
5
|
+
authority: string;
|
|
6
|
+
name: string;
|
|
7
|
+
image: string;
|
|
8
|
+
predictionsCount: number;
|
|
9
|
+
message: string;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useChat({
|
|
14
|
+
authority,
|
|
15
|
+
customerAuthority,
|
|
16
|
+
}: {
|
|
17
|
+
authority: string;
|
|
18
|
+
customerAuthority: string;
|
|
19
|
+
}) {
|
|
20
|
+
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
21
|
+
const [onlineCount, setOnlineCount] = useState(0);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!authority || !customerAuthority) return;
|
|
25
|
+
|
|
26
|
+
if (!socket.connected) {
|
|
27
|
+
socket.connect();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
socket.emit("chat:join", { authority, customerAuthority }, (res: any) => {
|
|
31
|
+
if (res.error) {
|
|
32
|
+
console.error("join error", res.error);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
socket.off("chat:history");
|
|
37
|
+
socket.on("chat:history", (msgs) => {
|
|
38
|
+
setMessages((prev) => (prev.length ? prev : msgs));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
socket.off("chat:message");
|
|
42
|
+
socket.on("chat:message", (msg) => {
|
|
43
|
+
setMessages((prev) => [...prev, msg]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
socket.off("chat:users");
|
|
47
|
+
socket.on("chat:users", (data) => {
|
|
48
|
+
setOnlineCount(data.count);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return () => {
|
|
52
|
+
socket.off("chat:history");
|
|
53
|
+
socket.off("chat:message");
|
|
54
|
+
socket.off("chat:users");
|
|
55
|
+
};
|
|
56
|
+
}, [authority, customerAuthority]);
|
|
57
|
+
|
|
58
|
+
const sendMessage = (message: string) => {
|
|
59
|
+
socket.emit(
|
|
60
|
+
"chat:message",
|
|
61
|
+
{ authority, customerAuthority, message },
|
|
62
|
+
(res: any) => {
|
|
63
|
+
if (res.error) {
|
|
64
|
+
console.error("send error", res.error);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
messages,
|
|
72
|
+
onlineCount,
|
|
73
|
+
sendMessage,
|
|
74
|
+
};
|
|
75
|
+
}
|
package/src/index.ts
ADDED
package/src/utils/cn.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import io from "socket.io-client";
|
|
2
|
+
|
|
3
|
+
const socket = io("https://beta.triadfi.co", {
|
|
4
|
+
path: "/socket.io",
|
|
5
|
+
reconnection: true,
|
|
6
|
+
reconnectionAttempts: 5,
|
|
7
|
+
reconnectionDelay: 1000,
|
|
8
|
+
randomizationFactor: 0.5,
|
|
9
|
+
timeout: 5000,
|
|
10
|
+
transports: ["websocket"],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export default socket;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2020",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"jsx": "react-jsx",
|
|
6
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"moduleResolution": "node",
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": {
|
|
17
|
+
"@/*": ["src/*"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist"]
|
|
22
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ['src/index.ts'],
|
|
5
|
+
format: ['cjs', 'esm'], // Build for commonJS and ESmodules
|
|
6
|
+
dts: true, // Generate declaration file (.d.ts)
|
|
7
|
+
splitting: false,
|
|
8
|
+
sourcemap: true,
|
|
9
|
+
clean: true,
|
|
10
|
+
treeshake: true,
|
|
11
|
+
external: ['react', 'react-dom', 'next'], // Never bundle Next/React
|
|
12
|
+
minify: true
|
|
13
|
+
});
|