UNPKG

@tabtree/workchat

Version:

Tabtree Workchat by AM

1 lines 298 kB
{"version":3,"file":"index.modern.mjs","sources":["../src/network.js","../src/ChatProvider.js","../src/AuthContext.js","../src/components/VideoPlayer.jsx","../src/components/AudioPlayer.jsx","../src/components/LinkHighlighter.jsx","../src/components/BeautifiedCodeViewer.jsx","../src/components/PreviewDialog.jsx","../src/components/MapCard.jsx","../src/chats/UserChat.jsx","../src/components/PollMessage.jsx","../src/chats/GroupChat.jsx","../src/chats/BotChat.jsx","../src/ChatWindows.js","../src/Workchat.js","../src/global.js"],"sourcesContent":["import axios from \"axios\";\n\nexport const AXIOS = axios;\n\n// Create the custom Axios instance\nconst axiosInstance = axios.create({\n baseURL: \"https://chatapi.nte.ai/v5\",\n // baseURL: \"https://chatstagingapi.nte.ai\",\n\n headers: {\n 'Content-Type': 'application/json',\n },\n});\n\n// Add a request interceptor to attach the auth token\naxiosInstance.interceptors.request.use(\n (config) => {\n const token = localStorage.getItem('authToken')\n console.log('tokentoken', token)\n\n // const token = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZDEiOiI2ODEzMGEyNjdjMDY4NWY1ZTg3OGI4NTQiLCJpZDIiOiI2MTEzOGQ1YS1lMGUxLTcwODMtYmFkNC1jYWZmNjI1MTg1M2QiLCJpZDMiOiJhbm9vcG1vaGFuQHRhYnRyZWUuaW4iLCJpZDQiOiI2ODExZjEwMDQ4YjFiZGQ2MDcyMzE4ODUiLCJpYXQiOjE3NDcwNDcxMzh9.VO-ZQZlYWLQhzGqwHoljzbey9LbK2onjDHkDKJ6nlTE\";\n\n if (token) {\n config.headers.Authorization = token;\n }\n return config;\n },\n (error) => {\n return Promise.reject(error);\n }\n);\n\nexport default axiosInstance;\n","import React, { useState, useEffect, useRef, createContext, useContext, useCallback, useMemo } from 'react';\nimport { MessageSquare, Send, X, Phone, Video, Minimize, Maximize, ChevronUp, MessageSquareText, Settings, UserRound } from 'lucide-react';\nimport axiosInstance from './network';\nimport moment from 'moment';\nimport { io } from \"socket.io-client\";\n\n// Create a context for the chat state\nconst ChatContext = createContext();\n\n// Custom hook to use the chat context\nexport const useChatContext = () => useContext(ChatContext);\n\n\n// Chat Provider Component\nexport const ChatProvider = ({ children }) => {\n const [activeChats, setActiveChats] = useState([]);\n const [activeChatsIds, setActiveChatsIds] = useState({});\n const [activeChatsMsg, setActiveChatsMsg] = useState({});\n const [openChatUserId, setOpenChatUserId] = useState(0)\n // console.log('activeChatsactiveChatsactiveChats', activeChats)\n\n const [minimizedChats, setMinimizedChats] = useState([]);\n\n\n\n\n const [minimizedContact, setMinimizedContact] = useState(true);\n const [newMessage, setNewMessage] = useState({});\n const [searchContactVal, setSearchContactVal] = useState(\"\");\n const [searchVal, setSearchVal] = useState(\"\");\n\n\n const textareaRef = useRef({});\n\n const messageEndRef = useRef({});\n // const MAX_CHATS = 3;\n const [activeTab, setActiveTab] = useState('chat');\n const [maxChat, setMaxChat] = useState(3)\n\n // State to hold the chat user list\n const [userList, setUserList] = useState([]);\n // State to hold the chat user list\n const [contactList, setContactList] = useState([]);\n const [ContactListSearch, setContactListSearch] = useState([]);\n const [chatListSearch, setChatListSearch] = useState([]);\n const [chatGroupMembers, setChatGroupMembers] = useState([])\n\n // State messages list\n const [messages, setMessages] = useState([])\n\n // state message input\n const [message, setMessage] = useState('')\n // state for chatid\n\n const [chatId, setChatId] = useState('')\n const [chats, setChats] = useState({})\n const [toUser, setToUser] = useState(\"\")\n const [socket, setSocket] = useState(null);\n const [messageAlert, setMessageAlert] = useState(false)\n const [messageWorkchatAlert, setMessageWorkchatAlert] = useState(false)\n const [messageWorkchatUserAlert, setMessageWorkchatUserAlert] = useState(false)\n const [updateUserListNotification, setUpdateUserListNotification] = useState(false)\n\n\n const activeChatRef = useRef(activeChats);\n const userChatRef = useRef(userList);\n const messageAlertRef = useRef(messageAlert);\n\n\n\n\n // FileUpload\n\n const [fileToUpload, setFileToUpload] = useState(null);\n const [showPreviewDialog, setShowPreviewDialog] = useState(false);\n const fileInputRef = useRef(null);\n const dropAreaRef = useRef(null);\n const [fileUrl, setFileUrl] = useState(\"\")\n const [isDragging, setIsDragging] = useState(false);\n const [activeGroupChat, setActiveGroupChat] = useState(\"\")\n const [activeBotChat, setActiveBotChat] = useState(\"\")\n const [activeUserChat, setActiveUserChat] = useState(\"\")\n\n\n // reply message\n\n const [replyMessageData, setReplyMessageData] = useState('')\n const messagesContainerRef = useRef(null);\n\n const [highlightReply, setHighlightReply] = useState('')\n\n\n // menton states\n const groupInputRef = useRef(null);\n const modalRef = useRef(null);\n\n\n const [showModal, setShowModal] = useState(false);\n const [modalPosition, setModalPosition] = useState({ top: 0, left: 0, width: 0 });\n const [mentionQuery, setMentionQuery] = useState('');\n const [mentionedUsers, setMentionedUsers] = useState([]);\n const filteredMentionGroupMembers = chatGroupMembers.filter(contact =>\n contact.userId.name.toLowerCase().includes(mentionQuery.toLowerCase())\n );\n\n const [activeChatOptions, setActiveChatOptions] = useState('All');\n const [isDisableMcpInput, setIsDisableMcpInput] = useState(false)\n\n const [chatPageLimit, setChatPageLimit] = useState(0)\n const [clickedAkc, setClickedAkc] = useState(false);\n\n\n const handleChatFilter = val => {\n\n setActiveChatOptions(val)\n fetchChatUserList(val)\n setActiveBotChat(\"\")\n setSearchVal(\"\")\n closeChat(openChatUserId)\n // toggleMinimize\n\n\n }\n\n // Cancel file upload\n const handleCancelFileUpload = () => {\n setFileToUpload(null);\n setShowPreviewDialog(false);\n fileInputRef.current.value = '';\n\n setNewMessage(prev => ({ ...prev, [toUser]: \"\" }));\n\n };\n\n // Trigger file input click\n const handleAttachmentClick = (id) => {\n setToUser(id)\n fileInputRef.current.click();\n };\n\n // Check if file is an image\n const isImageFile = (file) => {\n return file && file.type.startsWith('image/');\n };\n\n // Format file size\n const formatFileSize = (bytes) => {\n if (bytes < 1024) return bytes + ' B';\n else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';\n else return (bytes / 1048576).toFixed(1) + ' MB';\n };\n\n\n // FILE UPLOAD FUNCTIONS\n\n const handleDragOver = useCallback((e) => {\n e.preventDefault();\n e.stopPropagation();\n setIsDragging(true);\n }, []);\n\n const handleDragEnter = useCallback((e) => {\n e.preventDefault();\n e.stopPropagation();\n setIsDragging(true);\n }, []);\n\n const handleDragLeave = useCallback((e) => {\n e.preventDefault();\n e.stopPropagation();\n setIsDragging(false);\n }, []);\n\n const handleDrop = useCallback((e) => {\n e.preventDefault();\n e.stopPropagation();\n setIsDragging(false);\n\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n const file = e.dataTransfer.files[0];\n setFileToUpload(file);\n setShowPreviewDialog(true);\n }\n }, []);\n\n\n const handleFileSelect = (e) => {\n if (e.target.files.length > 0) {\n const file = e.target.files[0];\n setFileToUpload(file);\n setShowPreviewDialog(true);\n }\n };\n\n\n // const fetchUploadFileApi = async (file) => {\n // try {\n // var formData = new FormData();\n // formData.append(\"files\", fileToUpload);\n // // const response = axiosInstance.post('chatFile/fileUpload', payload)\n // const response = await axiosInstance.post(\n // `chatFile/fileUpload`,\n // formData,\n // {\n // headers: {\n // 'Content-Type': 'multipart/form-data',\n // },\n // }\n // );\n // if (response.data.status === 'success') {\n // console.log('fetchUploadFileApi', response.data)\n // setFileUrl(response.data.url)\n // setNewMessage(prev => ({ ...prev, [toUser]: fileToUpload.name, }));\n // }\n // } catch (error) {\n\n // }\n\n // }\n const [isUploadFileLoading, setIsUploadFileLoading] = useState(false)\n\n const fetchUploadFileApi = async () => {\n setIsUploadFileLoading(true)\n try {\n const uploadedUrls = [];\n const farr = [fileToUpload]\n const uploadTasks = farr.map(async (file) => {\n // 1. Get presigned URL\n const preSendResponse = await axiosInstance.post(\n `chatFile/filepresignedUpload`,\n { fileName: file.name, fileType: file.type }\n );\n\n const { presignedUrl, fileUrl } = preSendResponse.data;\n\n // 2. Upload to S3\n const uploadToS3 = await fetch(presignedUrl, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": \"application/octet-stream\",\n },\n body: file,\n });\n\n if (uploadToS3.status === 200) {\n uploadedUrls.push(fileUrl);\n } else {\n console.error(\"Failed upload:\", file.name, uploadToS3.status);\n }\n });\n\n // Run all uploads in parallel\n await Promise.all(uploadTasks);\n\n // 3. Update state after uploads\n setFileUrl(uploadedUrls[0]);\n setNewMessage(prev => ({ ...prev, [toUser]: fileToUpload.name, }));\n\n } catch (error) {\n console.log(\"Upload failed:\", error);\n } finally {\n setIsUploadFileLoading(false)\n }\n };\n\n\n\n\n useEffect(() => {\n activeChatRef.current = activeChats;\n }, [activeChats]);\n useEffect(() => {\n userChatRef.current = userList;\n if (userList.length > 0) {\n localStorage.setItem('userListStore', JSON.stringify(userList));\n }\n }, [userList]);\n useEffect(() => {\n messageAlertRef.current = messageAlert;\n }, [messageAlert]);\n const handleSwitchTab = (event) => {\n setActiveTab(event);\n setActiveChatOptions('All')\n if (event === \"contact\") {\n fetchContacts()\n\n } else {\n fetchChatUserList()\n }\n };\n\n // Fetches the full user list\n const fetchChatUserList = async (val) => {\n // setIsLoading(true)\n try {\n const response = await axiosInstance.get(\"chatUser/userList\");\n\n if (response.data.status === \"success\") {\n const users = response.data.response;\n\n let filteredUsers = users;\n\n if (val === \"Groups\") {\n filteredUsers = users.filter(x => x.type === \"group\");\n } else if (val === \"Unread\") {\n filteredUsers = users.filter(x => x.unReadCount > 0);\n } else if (val === \"AI\") {\n filteredUsers = users.filter(x => x.type === \"bot\");\n } else if (val === \"Broadcasts\") {\n filteredUsers = users.filter(x => x.type === \"broadcast\");\n }\n\n\n\n setUserList(filteredUsers);\n // setUserList(response.data.response);\n setChatListSearch(users);\n }\n } catch (error) {\n console.error(\"fetchChatUserList error:\", error);\n } finally {\n // setIsLoading(false)\n }\n };\n\n\n // Fetches the user list based on the search input\n\n const fetchSearchChatUserList = async () => {\n const trimmedVal = searchVal.trim();\n\n if (trimmedVal.length === 0) {\n fetchChatUserList();\n return;\n }\n\n try {\n const response = await axiosInstance.post(\"chatUser/search\", {\n name: trimmedVal,\n });\n\n console.log(\"fetchSearchChatUserList\", response.data);\n\n if (response.data.status === \"success\") {\n setUserList(response.data.userList || []);\n }\n } catch (error) {\n console.error(\"fetchSearchChatUserList error:\", error);\n }\n };\n\n // Fetches all contacts list\n\n const fetchContacts = async () => {\n try {\n const response = await axiosInstance.get(`chatUser/contactList`)\n\n console.log('fetchContacts', response.data)\n if (response.data.status === 'success') {\n setContactList(response.data.userData)\n setContactListSearch(response.data.userData)\n }\n\n } catch (error) {\n console.log('fetchContacts', error)\n\n } finally {\n // setIsLoading(false)\n }\n }\n\n\n\n const processChatHistory = (chatHistory, chatUserId, authuserId) => {\n const modifychat = chatHistory.map((msg) => {\n return {\n _id: msg._id,\n sender: msg.fromUserId._id !== authuserId ? 'reciver' : 'me',\n text: msg.messageContent,\n isRead: msg.isRead,\n user: msg.toUserId._id !== authuserId ? msg?.toUserId : msg?.fromUserId,\n time: moment(msg?.updatedAt).format('ddd hh:mm A'),\n type: msg?.messageType,\n link: msg?.fileUrl || \"\",\n fileType: msg?.fileType || \"\",\n createdAt: msg?.createdAt\n };\n });\n\n const grouped = {};\n modifychat.forEach((msg) => {\n const dateKey = moment(msg.createdAt).format(\"YYYY-MM-DD\");\n if (!grouped[dateKey]) {\n grouped[dateKey] = [];\n }\n grouped[dateKey].push(msg);\n });\n\n const data = Object.keys(grouped)\n .sort((a, b) => moment(b).diff(moment(a)))\n .map((date) => {\n const mDate = moment(date);\n let label = mDate.isSame(moment(), \"day\")\n ? \"Today\"\n : mDate.isSame(moment().subtract(1, \"day\"), \"day\")\n ? \"Yesterday\"\n : mDate.format(\"DD MMM YYYY\");\n\n return {\n date: label,\n chats: grouped[date],\n chatUserId: chatUserId\n };\n });\n\n return data;\n };\n\n\n\n\n\n // Fetch chat history \n // ✅ Add these state variables to your ChatProvider\n const [hasMoreMessages, setHasMoreMessages] = useState(true);\n const [hasMoreGroupMessages, setHasMoreGroupMessages] = useState(true);\n const hasMoreMessagesRef = useRef(true);\n const hasMoreGroupMessagesRef = useRef(true);\n\n // ✅ Fixed fetchChatHistory with cursor-based pagination\n // const fetchChatHistory = async (val, limit = 0) => {\n // // ✅ Stop if no more messages available\n // if (!hasMoreMessagesRef.current) {\n // console.log('✋ No more messages to fetch');\n // return;\n // }\n\n // try {\n // // ✅ For cursor-based pagination: \n // // - First call: lastId = \"\" (gets most recent 20)\n // // - Next calls: lastId = last message's _id (gets next 20 older messages)\n // const lastMessageId = (limit !== 0 && messages.length > 0)\n // ? messages[messages.length - 1]._id // Get the OLDEST message (last in array)\n // : \"\";\n\n // const dada = {\n // toUser: val,\n // limit: 20,\n // lastId: lastMessageId\n // };\n\n // console.log('📤 Fetching chat history:', {\n // ...dada,\n // currentMessagesCount: messages.length,\n // isFirstCall: limit === 0\n // });\n\n // const response = await axiosInstance.post(\"chatUser/chatHistory\", dada);\n\n // console.log('📥 fetchChatHistory response:', {\n // status: response.data.status,\n // receivedCount: response.data.chatHistory?.length || 0,\n // chatUserId: response.data.chatUserId\n // });\n\n // if (response.data.status === \"success\") {\n // const messageArr = response.data.chatHistory;\n\n // // ✅ Check if we've reached the end (empty array returned)\n // if (messageArr.length === 0) {\n // console.log('✅ No more messages available - end of pagination');\n // setHasMoreMessages(false);\n // hasMoreMessagesRef.current = false;\n // return;\n // }\n\n // // ✅ Check if we got fewer messages than requested (last page)\n // // if (messageArr.length < 20) {\n // // console.log('✅ Last page reached - got', messageArr.length, 'messages');\n // // setHasMoreMessages(false);\n // // hasMoreMessagesRef.current = false;\n // // }\n\n // // ✅ Append older messages to the END of the array\n // setMessages((prev) => [...prev, ...messageArr]);\n\n // // Update chat IDs\n // if (response.data.chatUserId && response.data.chatUserId !== \"\") {\n // setActiveChatsIds(prev => {\n // const existing = prev[val] || [];\n\n // // Avoid adding duplicate\n // if (existing.includes(response.data.chatUserId)) {\n // return prev;\n // }\n\n // return {\n // ...prev,\n // [val]: [...existing, response.data.chatUserId],\n // };\n // });\n // }\n\n // setChatPageLimit((prev) => prev + 1);\n // }\n // } catch (error) {\n // console.error('❌ fetchChatHistory error:', error);\n // // Don't block future fetches on error\n // }\n // };\n\n // Fetch user chat history \n const fetchChatHistory = async (val, limit = 0) => {\n try {\n\n let dada = {\n toUser: val,\n limit: 20,\n lastId: (limit !== 0) ? messages.at(-1)._id : null\n }\n\n const response = await axiosInstance.post(\"chatUser/chatHistory\", dada);\n\n\n if (response.data.status === \"success\") {\n let messageArr = response.data.chatHistory\n\n if (response.data.chatUserId != \"\") {\n // setChatId(response.data.chatUserId)\n setActiveChatsIds(prev => {\n const existing = prev[val] || [];\n\n // Avoid adding duplicate\n if (existing.includes(response.data.chatUserId)) {\n return prev;\n }\n\n return {\n ...prev,\n [val]: [...existing, response.data.chatUserId],\n };\n });\n // fetchChatMedia(response.data.chatUserId)\n }\n if (messageArr.length === 0) {\n console.log('✅ No more messages available - end of pagination');\n setHasMoreMessages(false);\n hasMoreMessagesRef.current = false;\n return;\n }\n setMessages((prev) => [...prev, ...messageArr]);\n\n\n\n\n setChatPageLimit((prev) => prev + 1);\n }\n } catch (error) {\n console.log('fetchGroupChatHistory', error)\n }\n }\n\n // Fetch group chat history \n const fetchGroupChatHistory = async (val, limit = 0) => {\n try {\n\n let dada = {\n limit: 20,\n lastId: (limit !== 0) ? messages.at(-1)._id : null\n }\n\n const response = await axiosInstance.post(`/group/history/${val.chatUserId}`, dada);\n\n\n if (response.data.status === \"success\") {\n let messageArr = response.data.chatHistory\n\n setActiveChatsIds(val.chatUserId)\n if (messageArr.length === 0) {\n console.log('✅ No more messages available - end of pagination');\n setHasMoreMessages(false);\n hasMoreMessagesRef.current = false;\n return;\n }\n setMessages((prev) => [...prev, ...messageArr]);\n\n\n\n\n setChatPageLimit((prev) => prev + 1);\n }\n } catch (error) {\n console.log('fetchGroupChatHistory', error)\n }\n }\n\n // Fetch chat Group info\n const fetchChatGroupInfo = async (val) => {\n try {\n\n const response = await axiosInstance.get(`/group/groupInfo/${val}`);\n\n console.log('fetchChatGroupInfo', response.data)\n\n if (response.data.status === \"success\") {\n\n setChatGroupMembers(response.data.groupInfo.userList)\n\n } else {\n setChatGroupMembers([])\n }\n\n } catch (error) {\n console.log('fetchChatGroupInfo', error)\n }\n }\n\n const fetchBotChatHistory = async (val, limit = 0) => {\n try {\n\n\n let dada = {\n limit: 20,\n lastId: (limit !== 0) ? messages.at(-1)._id : null\n }\n\n const response = await axiosInstance.post(`/bot/chatHistory/${val.chatUserId}`, dada);\n\n // const response = await axiosInstance.get(`/bot/chatHistory/${val.chatUserId}`);\n\n console.log('fetchBotChatHistory', response.data)\n\n if (response.data.status === \"success\") {\n let messageArr = response.data.chatList\n if (messageArr.length === 0) {\n console.log('✅ No more messages available - end of pagination');\n setHasMoreMessages(false);\n hasMoreMessagesRef.current = false;\n return;\n }\n setMessages(messageArr)\n if (response.data?.hasMcp) {\n setIsDisableMcpInput(true)\n } else {\n setIsDisableMcpInput(false)\n\n }\n setChatPageLimit((prev) => prev + 1);\n\n }\n } catch (error) {\n console.log('fetchBotChatHistory', error)\n }\n }\n\n // read bot message api \n const fetchReadBotMessage = async (val) => {\n try {\n\n\n\n const response = await axiosInstance.get(`bot/readBotMessage/${val}`);\n\n console.log('fetchChannelChatHistory', response.data)\n\n if (response.data.status === \"success\") {\n const newData = userChatRef.current.map(obj =>\n obj.chatUserId === val\n ? { ...obj, unReadCount: 0 }\n : obj\n );\n\n const isEqual = JSON.stringify(userList) === JSON.stringify(newData); // or use deepEqual if needed\n\n userChatRef.current = newData;\n\n if (!isEqual) {\n setUserList(newData);\n }\n }\n } catch (error) {\n console.log('fetchChannelChatHistory', error)\n }\n }\n\n\n\n useEffect(() => {\n const authuserId = localStorage.getItem(\"authUser\");\n\n\n if (toUser === \"\") return\n\n\n let uu = activeChats.includes(toUser)\n const found = activeChats.find(id => id === toUser) || toUser\n\n\n const modifychat = messages.map((msg) => {\n const senderId = msg.fromUserId?._id || msg?.userId?._id;\n const isSender = senderId === authuserId;\n const user =\n msg.toUserId?._id !== authuserId ? msg.toUserId : msg.fromUserId;\n\n if (activeUserChat.type === \"user\") {\n return {\n _id: msg._id,\n sender: isSender ? \"me\" : \"reciver\",\n text: msg.messageContent,\n isRead: msg.isRead,\n user,\n time: moment(msg?.updatedAt).format(\"ddd hh:mm A\"),\n type: msg?.messageType,\n link: msg?.fileUrl || \"\",\n fileType: msg?.fileType || \"\",\n createdAt: msg?.createdAt,\n isForwarded: msg.isForwarded,\n isDeleted: msg.isDeleted,\n isStared: msg.isStared,\n replyMessageId: msg.replyMessageId || \"\",\n fileName: msg.fileName,\n pollMessage: msg.pollMessage,\n isBroadCastAcknowledge: msg?.isBroadCastAcknowledge,\n broadCastMessageId: msg?.broadCastMessageId,\n\n };\n } else if (activeBotChat.type === \"bot\") {\n return {\n\n _id: msg._id,\n // groupId: msg.groupId,\n sender: msg.messageType === ('M' || \"AI\" || \"doc\" || 'cta') ? \"me\" : \"reciver\",\n text: msg.messageContent,\n // isRead: msg.isRead,\n // user,\n time: moment(msg?.updatedAt).format(\"ddd hh:mm A\"),\n type: msg?.messageType,\n link: msg?.fileUrl || \"\",\n fileType: msg?.fileType || \"\",\n createdAt: msg?.createdAt,\n // name: msg?.userId?.name,\n ctaInfo: msg?.ctaInfo || \"\",\n ctaData: msg.ctaData,\n fileName: msg.fileName\n };\n } else {\n return {\n _id: msg._id,\n sender: isSender ? \"me\" : \"reciver\",\n text: msg.messageContent,\n isRead: msg.isRead,\n user: msg.userId,\n time: moment(msg?.updatedAt).format(\"ddd hh:mm A\"),\n type: msg?.messageType,\n link: msg?.fileUrl || \"\",\n fileType: msg?.fileType || \"\",\n createdAt: msg?.createdAt,\n name: msg?.userId?.name,\n isForwarded: msg.isForwarded,\n isDeleted: msg.isDeleted,\n isStared: msg.isStared,\n replyMessageId: msg.replyMessageId || \"\",\n fileName: msg.fileName,\n pollMessage: msg.pollMessage,\n\n\n }\n }\n });\n console.log('modifychat', modifychat);\n\n\n const grouped = {};\n\n\n\n modifychat.forEach((msg) => {\n const dateKey = moment(msg.createdAt).format(\"YYYY-MM-DD\"); // for grouping\n if (!grouped[dateKey]) {\n grouped[dateKey] = [];\n }\n grouped[dateKey].push(msg);\n });\n\n // Build the final array with label\n const data = Object.keys(grouped)\n .sort((a, b) => moment(b).diff(moment(a))) // Optional: latest date first\n .map((date) => {\n const mDate = moment(date);\n let label = mDate.isSame(moment(), \"day\")\n ? \"Today\"\n : mDate.isSame(moment().subtract(1, \"day\"), \"day\")\n ? \"Yesterday\"\n : mDate.format(\"DD MMM YYYY\");\n\n return {\n date: label,\n chats: grouped[date],\n chatUserId: activeChatsIds[toUser] //|| activeChatsIds[toUser][0]\n };\n });\n\n\n if (activeChats.length >= maxChat) {\n if (!activeChats.includes(found)) {\n const newChats = [...activeChats];\n const oldestChat = newChats.shift(); // Remove the oldest chat (first in array)\n newChats.push(found); // Add the new chat\n setActiveChats(newChats);\n }\n setChats(prev => ({ ...prev, [found]: data }));\n } else {\n // Otherwise just add the new chat\n\n setActiveChats(prev => (\n prev.includes(found) ? prev : [...prev, found]\n ));\n\n\n setChats(prev => ({ ...prev, [found]: data }));\n\n // console.log('[found]: data', found, { [found]: data })\n }\n\n // Focus on the new chat input after it's rendered\n setTimeout(() => {\n const inputElement = document.getElementById(`chat-input-${found}`);\n if (inputElement) {\n inputElement.focus();\n }\n }, 100);\n }, [messages, activeBotChat, activeUserChat])\n\n\n function moveObject(array, id, newIndex) {\n // console.log('array', array);\n // console.log(' id, ', id);\n // console.log(' newIndex', newIndex);\n\n if (!Array.isArray(array)) {\n console.error(\"Expected an array, but got:\", array);\n return [];\n }\n\n\n const elementIndex = array.findIndex((element) => element._id == id);\n\n if (elementIndex === -1) {\n return array;\n }\n\n const element = array.splice(elementIndex, 1)[0];\n array.splice(newIndex, 0, element);\n\n return array;\n }\n\n // useMemo(() => {\n // try {\n // Notification.requestPermission()\n // } catch (error) {\n // console.error(\"Notification.requestPermission()\")\n // }\n // }, [])\n\n useEffect(() => {\n const token = localStorage.getItem('authToken');\n const authuserId = localStorage.getItem(\"authUser\");\n\n if (!token) return;\n\n const apiUrl = `https://chatapi.nte.ai/chat`;\n // const apiUrl = `https://chatstagingapi.nte.ai/chat`;\n // const apiUrl = `http://172.16.5.25:3005/chat`;\n\n\n console.log('apiUrl', apiUrl)\n if (!apiUrl) {\n console.error(\"REACT_APP_API_URL not defined in environment\");\n return;\n }\n\n const newSocket = io(apiUrl, {\n transports: ['websocket'],\n auth: { token },\n // autoConnect: true,\n // reconnection: true,\n // reconnectionAttempts: 5,\n // reconnectionDelay: 1000\n });\n\n newSocket.on('connect', () => {\n console.log('Connected to socket server with ID:', newSocket.id);\n\n\n\n });\n\n\n newSocket.on('disconnect', (reason) => {\n console.log('Socket disconnected:', reason);\n // setSocketConnected(false);\n });\n\n newSocket.on('receiveMessage', (data) => {\n const authuserId = localStorage.getItem(\"authUser\");\n console.log('Received message:', data);\n\n // showNotification(data)\n\n if (!activeChatRef.current.includes(data.fromUserId._id)) {\n setMessageWorkchatAlert(true)\n }\n\n\n // Ensure it's an array before calling moveObject\n if (Array.isArray(userChatRef.current)) {\n const newArray = moveObject([...userChatRef.current], data.fromUserId._id, 0); // shallow copy for immutability\n\n\n\n const newData = newArray.map(obj =>\n obj._id == data.fromUserId._id ? { ...obj, isDeleted: false, messageContent: data.messageContent, messageId: data.messageId, createdAt: data.createdAt, msg: \"New\", unReadCount: activeChatRef.current.includes(data.fromUserId._id) ? 0 : (obj.unReadCount < 0 ? 1 : obj.unReadCount + 1) } : obj\n );\n // console.log('newDatanewDatanewData', newData)\n userChatRef.current = newData;\n\n setUserList(newData);\n } else {\n console.error(\"userChatRef.current is not an array:\", userChatRef.current);\n }\n setMessageAlert(false)\n\n // console.log('activeChatRef.current', activeChatRef.current.includes(data.fromUserId._id), activeChatRef.current, data.fromUserId._id, data.toUserId._id)\n if (!activeChatRef.current.includes(data.fromUserId._id)) return\n\n messageAlertRef.current = true;\n setMessageAlert(true)\n setMessages((prevMessages) => [data, ...prevMessages]);\n\n newSocket.emit(\"readMessage\", { user_id: data.fromUserId._id })\n\n\n\n });\n\n newSocket.on('redMessage', (payload) => {\n // console.log('redMessage received:', payload, activeChatRef.current);\n // alert(payload)\n\n setMessages((prevMessages) =>\n prevMessages.map((msg) =>\n activeChatRef.current.includes(payload) ? { ...msg, isRead: true } : msg\n )\n );\n\n\n\n });\n\n\n\n newSocket.on('ownMessage', (data,) => {\n\n console.log('ownMessage', data)\n\n\n\n if (Array.isArray(userChatRef.current)) {\n const newArray = moveObject([...userChatRef.current], data.toUserId._id, 0); // shallow copy for immutability\n\n\n\n const newData = newArray.map(obj =>\n obj._id == data.toUserId._id ? { ...obj, messageContent: data.messageContent, messageId: data.messageId, createdAt: data.createdAt, msg: \"Read\", isDeleted: false, } : obj\n );\n // console.log('newDatanewDatanewData', newData)\n userChatRef.current = newData;\n\n setUserList(newData);\n } else {\n console.error(\"userChatRef.current is not an array:\", userChatRef.current);\n }\n // console.log('activeChatRef.current', activeChatRef.current.includes(data.toUserId._id), activeChatRef.current, data.toUserId._id)\n\n if (!activeChatRef.current.includes(data.toUserId._id)) return\n\n setMessages((prevMessages) => [data, ...prevMessages,]);\n\n });\n\n\n newSocket.on('deletedMessage', (data) => {\n // \n console.log('deletedMessage response:', data, data.fromUserId, activeChatRef.current._id);\n setUserList((prevUsers) =>\n prevUsers.map((user) =>\n user.chatUserId === data.chatUserId && data._id === user.messageId\n ? { ...user, isDeleted: true }\n : user\n )\n\n );\n if (!activeChatRef.current.includes(data.fromUserId)) return\n // setMessages((prevMessages) =>\n // prevMessages.filter((msg) => msg._id !== data._id)\n // );\n setMessages((prevMessages) =>\n prevMessages.map((msg) =>\n msg._id === data._id ? { ...msg, isDeleted: true } : msg\n )\n );\n })\n\n\n newSocket.on('receiveGroupMessage', (data,) => {\n\n // console.log('receiveGroupMessage', data, data.groupId, activeChatRef, activeChatRef.current.includes(data.groupId)\n //activeGroupChatRef.current.chatUserId\n\n // )\n\n if (Array.isArray(userChatRef.current)) {\n const newArray = moveObject([...userChatRef.current], data.groupId, 0); // shallow copy for immutability\n\n // console.log('newDatanewDatanewDataarr', newArray)\n\n\n const newData = newArray.map(obj =>\n obj.chatUserId == data.groupId ? { ...obj, messageContent: data.messageContent, messageId: data.messageId, createdAt: data.createdAt, isDeleted: false } : obj\n );\n // console.log('newDatanewDatanewData', newData)\n userChatRef.current = newData;\n\n setUserList(newData);\n } else {\n console.error(\"newDatanewDatanewData.current is not an array:\", userChatRef.current);\n }\n\n if (data?.userId?._id !== authuserId) {\n // showGroupNotification(data)\n }\n\n\n // showGroupNotification(data)\n\n\n // if (data.groupId !== activeGroupChatRef.current.chatUserId) return old\n\n\n if (!activeChatRef.current.includes(data.groupId)) return\n // setMessages((prevMessages) => [...prevMessages, data]);\n setMessages((prevMessages) => [data, ...prevMessages,]);\n // fetchGroupChatMedia(data.groupId)\n\n\n\n });\n\n newSocket.on('deleteGroupMessage', (data) => {\n // setMessages((prevMessages) =>\n // prevMessages.filter((msg) => msg._id !== data.messageId)\n // );\n\n // setMessagesForSearch((prevMessages) =>\n // prevMessages.filter((msg) => msg._id !== data.messageId)\n // );\n console.log('deleteGroupMessage', data)\n setMessages((prevMessages) =>\n prevMessages.map((msg) =>\n msg._id === data.messageId ? { ...msg, isDeleted: true, } : msg\n )\n );\n\n if (Array.isArray(userChatRef.current)) {\n const newArray = moveObject([...userChatRef.current], data.groupId, 0); // shallow copy for immutability\n\n\n\n const newData = newArray.map(obj =>\n obj.chatUserId == data.groupId && obj.messageId === data.messageId ? { ...obj, createdAt: new Date(), isDeleted: true, } : obj\n );\n userChatRef.current = newData;\n\n setUserList(newData);\n } else {\n console.error(\"newDatanewDatanewData.current is not an array:\", userChatRef.current);\n }\n })\n\n newSocket.on('deleteGroupMessage', (data) => {\n // setMessages((prevMessages) =>\n // prevMessages.filter((msg) => msg._id !== data.messageId)\n // );\n\n // setMessagesForSearch((prevMessages) =>\n // prevMessages.filter((msg) => msg._id !== data.messageId)\n // );\n console.log('deleteGroupMessage', data)\n setMessages((prevMessages) =>\n prevMessages.map((msg) =>\n msg._id === data.messageId ? { ...msg, isDeleted: true } : msg\n )\n );\n\n\n\n if (Array.isArray(userChatRef.current)) {\n const newArray = moveObject([...userChatRef.current], data.groupId, 0); // shallow copy for immutability\n\n console.log('newDatanewDatanewDataarr', newArray)\n\n\n const newData = newArray.map(obj =>\n obj.chatUserId == data.groupId ? { ...obj, createdAt: new Date(), isDeleted: true } : obj\n );\n console.log('newDatanewDatanewsadasData', newData)\n userChatRef.current = newData;\n\n setUserList(newData);\n } else {\n console.error(\"newDatanewDatanewData.current is not an array:\", userChatRef.current);\n }\n\n\n\n })\n\n newSocket.on('botMessage', (data) => {\n console.log('botMessage response:', data);\n\n // if (data.toUser !== activeBotRef.current._id) return\n if (Array.isArray(userChatRef.current)) {\n const newArray = moveObject([...userChatRef.current], data.toUser, 0); // shallow copy for immutability\n\n console.log('newDatanewDatanewDataarr', newArray)\n\n\n const newData = newArray.map(obj =>\n obj._id == data.toUser ? { ...obj, messageContent: data.messageContent, createdAt: data.createdAt } : obj\n );\n console.log('newDatanewDatanewData', newData)\n userChatRef.current = newData;\n\n setUserList(newData);\n } else {\n console.error(\"newDatanewDatanewData.current is not an array:\", userChatRef.current);\n }\n\n if (!activeChatRef.current.includes(data.toUser)) return\n setMessages((prevMessages) => [data, ...prevMessages]);\n });\n\n\n newSocket.on('initiateBot', (data) => {\n console.log('botMessageinitiateBot response:', data);\n\n // if (data.toUser !== activeBotRef.current._id) return\n if (Array.isArray(userChatRef.current)) {\n const newArray = moveObject([...userChatRef.current], data.toUser, 0); // shallow copy for immutability\n\n console.log('newDatanewDatanewDataarr', newArray)\n\n\n const newData = newArray.map(obj =>\n obj._id == data.toUser ? { ...obj } : obj\n );\n console.log('newDatanewDatanewData', newData)\n userChatRef.current = newData;\n\n setUserList(newData);\n } else {\n console.error(\"newDatanewDatanewData.current is not an array:\", userChatRef.current);\n }\n });\n\n newSocket.on('pollUpdated', data => {\n console.log('pollUpdated', data, activeChatRef.current, data.groupId);\n if ((activeChatRef.current.includes(data.groupId))) {\n\n setMessages(prev =>\n prev.map(msg =>\n msg._id === data.updatedPoll._id\n ? { ...msg, pollMessage: data.updatedPoll.pollMessage }\n : msg,\n ),\n );\n }\n });\n\n setSocket(newSocket);\n\n // Cleanup function\n return () => {\n if (newSocket) {\n newSocket.disconnect();\n }\n\n };\n }, []);\n\n\n\n const showNotification = (data) => {\n if ('Notification' in window && Notification.permission === 'granted') {\n // new Notification(title, {\n // body: body,\n // icon: '../../public/applogo.png' // Optional\n // });\n const notification = new Notification(`New message from ${data?.fromUserId?.name}`, {\n body: data.messageContent,\n icon: '/applogo.png',// fix: no `../../public`\n\n data: data // optional custom data\n });\n\n // Handle click\n notification.onclick = function (event) {\n event.preventDefault(); // prevent default behavior\n window.focus();\n const storedList = JSON.parse(localStorage.getItem('userListStore') || '[]');\n // console.log('storedListstoredList', storedList)\n let findUser = storedList.find(x => x._id === notification.data.fromUserId._id)\n if (findUser) {\n setMessageWorkchatAlert(false)\n setMinimizedChats([])\n handleUserClick(findUser);\n setUpdateUserListNotification(true)\n\n } else {\n console.log('User not found in list at the time of click.');\n }\n\n\n\n // window.location.href = notification.data.url || '/';\n };\n }\n\n\n };\n\n const showGroupNotification = (data) => {\n if ('Notification' in window && Notification.permission === 'granted') {\n\n const notification = new Notification(`New message from ${data.userId.name}`, {\n body: data.messageContent,\n icon: '/applogo.png',// fix: no `../../public`\n\n data: data // optional custom data\n });\n\n // Handle click\n notification.onclick = function (event) {\n event.preventDefault(); // prevent default behavior\n window.focus();\n\n\n const storedList = JSON.parse(localStorage.getItem('userListStore') || '[]');\n // console.log('storedListstoredList', storedList)\n let findUser = storedList.find(x => x._id === notification.data.userId._id)\n console.log('notification.data.url', findUser)\n if (findUser) {\n setMessageWorkchatAlert(false)\n setMinimizedChats([])\n handleUserClick(findUser);\n setUpdateUserListNotification(true)\n\n } else {\n console.log('User not found in list at the time of click.');\n }\n\n\n\n };\n }\n\n\n };\n\n useEffect(() => {\n\n const storedList = JSON.parse(localStorage.getItem('userListStore') || '[]');\n\n if (userList.length == 0 && updateUserListNotification) {\n setUserList(storedList)\n setTimeout(() => {\n setUpdateUserListNotification(false)\n }, 200);\n }\n\n\n\n }, [updateUserListNotification])\n\n // Store chat state in localStorage when it changes\n useEffect(() => {\n if (typeof window !== 'undefined') {\n localStorage.setItem('activeChats', JSON.stringify(activeChats));\n localStorage.setItem('minimizedChats', JSON.stringify(minimizedChats));\n localStorage.setItem('newMessage', JSON.stringify(chats));\n }\n }, [activeChats, minimizedChats, chats]);\n\n // Load chat state from localStorage on initial render\n useEffect(() => {\n if (typeof window !== 'undefined') {\n const storedActiveChats = localStorage.getItem('activeChats');\n const storedMinimizedChats = localStorage.getItem('minimizedChats');\n const storedNewMessage = localStorage.getItem('newMessage');\n\n if (storedActiveChats) setActiveChats(JSON.parse(storedActiveChats));\n if (storedMinimizedChats) setMinimizedChats(JSON.parse(storedMinimizedChats));\n if (storedNewMessage) setNewMessage(JSON.parse(storedNewMessage));\n }\n }, []);\n\n // Add or focus chat when a user is clicked\n const handleUserClick = (userId) => {\n setChatId('')\n setMessages([])\n setToUser(userId?._id)\n setShowModal(false)\n setMentionedUsers([])\n setChatPageLimit(0); // ✅ Reset page limit\n\n // ✅ Reset \"has more messages\" flags\n setHasMoreMessages(true);\n setHasMoreGroupMessages(true);\n hasMoreMessagesRef.current = true;\n hasMoreGroupMessagesRef.current = true;\n if (userId?.type === \"group\") {\n setActiveUserChat(\"\")\n\n fetchGroupChatHistory(userId)\n setActiveGroupChat(userId)\n fetchChatGroupInfo(userId?.chatUserId)\n\n } else if (userId?.type === \"bot\") {\n // setActiveGroupChat('')\n // setActiveUserChat(\"\")\n\n setChatId(userId?.chatUserId || \"\");\n\n\n fetchBotChatHistory(userId)\n setActiveBotChat(userId)\n if (userId?.chatUserId) {\n fetchReadBotMessage(userId.chatUserId);\n }\n } else {\n setActiveGroupChat('')\n\n fetchChatHistory(userId?._id, 0)\n setActiveUserChat(userId)\n\n }\n\n\n socket?.emit(\"readMessage\", { user_id: userId?._id })\n // reset()\n const newData = userChatRef.current.map(obj =>\n obj._id == userId?._id\n ? { ...obj, msg: \"Read\", unReadCount: 0 }\n : obj\n );\n\n const isEqual = JSON.stringify(userList) === JSON.stringify(newData); // or use deepEqual if needed\n\n userChatRef.current = newData;\n // console.log(' ', newData[0])\n\n if (!isEqual) {\n setUserList(newData);\n }\n\n\n // const newData = userList.map(obj =>\n // obj._id == userId?._id ? { ...obj, msg: \"Read\" } : obj\n // );\n // console.log('userIduserId', userId)\n\n // setUserList(newData);\n // If chat is already open, just focus it\n\n };\n\n // FIXED: Toggle minimize chat function using functional updates\n