@pubuduth-aplicy/chat-ui 2.1.73 → 2.1.74

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.
@@ -1,205 +1,164 @@
1
- import { useEffect, useState } from "react";
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { MessageCircle } from "lucide-react";
3
+ import { useEffect } from "react";
2
4
  import { useChatContext } from "../../providers/ChatProvider";
3
5
  import useChatUIStore from "../../stores/Zustant";
4
6
  import { ConversationProps } from "../../types/type";
5
- import { getChatConfig } from "@pubuduth-aplicy/chat-ui";
6
7
 
7
8
  const Conversation = ({ conversation }: ConversationProps) => {
8
9
  const {
9
10
  setSelectedConversation,
10
11
  setOnlineUsers,
11
- onlineUsers,
12
- setMessages,
13
- selectedConversation,
14
- updateMessageStatus,
15
12
  } = useChatUIStore();
16
- const { socket, sendMessage, isUserOnline } = useChatContext();
17
- const { role } = getChatConfig();
18
- // const handleSelectConversation = async () => {
19
- // setSelectedConversation(conversation);
20
-
21
- // // Mark unread messages as read
22
- // const unreadMessages = conversation.unreadMessageIds || [];
23
- // if (selectedConversation?._id && socket?.readyState === WebSocket.OPEN) {
24
- // sendMessage({
25
- // event: "joinChat",
26
- // data: {
27
- // chatId: conversation._id,
28
- // },
29
- // // event: "messageRead",
30
- // // data: {
31
- // // messageIds: unreadMessages,
32
- // // chatId: conversation._id,
33
- // // },
34
- // });
35
- // }
36
- // };
13
+ const { socket, isUserOnline } = useChatContext();
14
+ const selectedConversation = useChatUIStore(
15
+ (state) => state.selectedConversation
16
+ );
17
+ const { userId } = useChatContext();
37
18
 
38
- const [activeChatId, setActiveChatId] = useState(null);
19
+ // This should return the other participant
20
+ const participant = conversation.participantDetails?.find(
21
+ (p:any) => p._id !== userId
22
+ );
39
23
 
40
- const handleSelectConversation = async () => {
41
- // Set as selected conversation
42
- setSelectedConversation(conversation);
43
24
 
44
- // Mark as active chat
45
- setActiveChatId(conversation._id);
25
+ // const handleSelectConversation = () => {
26
+ // console.log("Selected Conversation Data:", JSON.stringify(conversation, null, 2));
27
+ // setSelectedConversation(conversation);
28
+
29
+ // const unreadMessages = conversation.unreadMessageIds || [];
30
+ // if (unreadMessages.length > 0 && socket?.readyState === WebSocket.OPEN) {
31
+ // console.log('unread meassge',unreadMessages);
32
+
33
+ // const message = {
34
+ // event: "messageRead",
35
+ // data: {
36
+ // messageIds: unreadMessages,
37
+ // senderId: participant?._id,
38
+ // chatId: conversation._id,
39
+ // },
40
+ // };
41
+ // socket.send(JSON.stringify(message));
42
+ // }
43
+ // };
46
44
 
47
- // Join chat via WebSocket
48
- if (socket?.readyState === WebSocket.OPEN) {
49
- sendMessage({
50
- event: "joinChat",
45
+ const handleSelectConversation = () => {
46
+ console.log(
47
+ "Selected Conversation Data:",
48
+ JSON.stringify(conversation, null, 2)
49
+ );
50
+ setSelectedConversation(conversation);
51
+ const unreadMessageIds = conversation.unreadMessageIds || [];
52
+ if (unreadMessageIds.length > 0 && socket?.readyState === WebSocket.OPEN) {
53
+ console.log("unread messages", unreadMessageIds);
54
+
55
+ const message = {
56
+ event: "messageRead",
51
57
  data: {
58
+ messageIds: unreadMessageIds,
59
+ senderId: participant?._id,
60
+ receiverId: userId,
52
61
  chatId: conversation._id,
53
- // Send any existing unread messages to mark as read
54
- messageIds: conversation.unreadMessageIds || [],
55
62
  },
56
- });
63
+ };
64
+ socket.send(JSON.stringify(message));
65
+ // updateConversationReadStatus(conversation._id, unreadMessageIds);
57
66
  }
58
67
  };
59
68
 
60
- // // Enhanced message handler
61
69
  useEffect(() => {
62
70
  if (!socket) return;
63
71
 
64
72
  const handleMessage = (event: MessageEvent) => {
65
73
  try {
66
- const message = JSON.parse(event.data);
67
- console.log("fdgfd", message);
68
-
69
- if (message.event === "newMessage") {
70
- const newMessage = message.data;
71
-
72
- // If this is the active chat, mark as read immediately
73
- if (activeChatId === newMessage.conversationId) {
74
- console.log("rtrtr");
75
-
76
- sendMessage({
77
- event: "messageRead",
78
- data: {
79
- messageIds: [newMessage._id],
80
- chatId: newMessage.conversationId,
81
- senderId: newMessage.senderId,
82
- },
83
- });
84
-
85
- // Optimistic UI update
86
- // updateMessageStatus(newMessage._id, "read");
87
- } else {
88
- // Otherwise mark as delivered
89
- sendMessage({
90
- event: "confirmDelivery",
91
- data: {
92
- messageIds: [newMessage._id],
93
- chatId: newMessage.conversationId,
94
- },
95
- });
96
- }
74
+ const data = JSON.parse(event.data);
75
+ if (data.event === "getOnlineUsers") {
76
+ setOnlineUsers(data.payload);
97
77
  }
98
78
  } catch (error) {
99
- console.error("Error handling message:", error);
79
+ console.error("Failed to parse WebSocket message:", error);
100
80
  }
101
81
  };
102
82
 
103
83
  socket.addEventListener("message", handleMessage);
104
- return () => socket.removeEventListener("message", handleMessage);
105
- }, [socket, activeChatId, setMessages, updateMessageStatus]);
106
84
 
107
- const isOnline = isUserOnline(conversation?.participantDetails?._id || "");
108
- console.log("Online status:", isOnline);
85
+ return () => {
86
+ socket.removeEventListener("message", handleMessage);
87
+ };
88
+ }, [socket, setOnlineUsers]);
89
+
90
+ // Temporary debug in your component
91
+ useEffect(() => {
92
+ console.log("Current conversation state:", conversation);
93
+ }, [conversation]);
94
+
95
+ const isOnline = isUserOnline(participant?._id || "");
109
96
  const isSelected = selectedConversation?._id === conversation._id;
97
+ const unreadCount = conversation.unreadMessageCount || 0;
98
+ const conversationName =
99
+ conversation.type === "service" && conversation.bookingId
100
+ ? `Booking #${conversation.bookingId}`
101
+ : participant?.firstname || "Conversation";
102
+
103
+ const lastMessageTimestamp = new Date(
104
+ conversation.lastMessage?.updatedAt || conversation.lastMessage?.createdAt
105
+ ).toLocaleTimeString([], {
106
+ hour: "2-digit",
107
+ minute: "2-digit",
108
+ });
110
109
 
111
110
  return (
112
- <>
113
- <div
114
- className={`conversation-container ${isSelected ? "selected" : ""} `}
115
- onClick={handleSelectConversation}
116
- >
117
- <div className="conversation-avatar">
118
- <img
119
- className="conversation-img"
120
- src={
121
- role === "admin" &&
122
- Array.isArray(conversation?.participantDetails)
123
- ? conversation.participantDetails[1]?.profilePic
124
- : !Array.isArray(conversation?.participantDetails)
125
- ? conversation?.participantDetails?.profilePic
126
- : undefined
127
- }
128
- alt="User Avatar"
129
- />
130
- <span
131
- className={`chatSidebarStatusDot ${isOnline && "online"}`}
132
- ></span>
133
- </div>
111
+ <div
112
+ className={`flex items-center p-2 cursor-pointer rounded-md hover:bg-gray-100 ${
113
+ isSelected ? "bg-gray-200" : ""
114
+ }`}
115
+ onClick={handleSelectConversation}
116
+ >
117
+ <div className="relative">
118
+ {conversation.type === "service" ? (
119
+ <div className="gap-2 flex relative">
120
+ <MessageCircle className="text-gray-600 w-4 h-4 mt-.5" />
121
+ </div>
122
+ ) : (
123
+ <>
124
+ <img
125
+ className="w-10 h-10 rounded-full"
126
+ src={participant?.profilePic}
127
+ alt="User Avatar"
128
+ />
129
+ <span
130
+ className={`chatSidebarStatusDot ${isOnline && "online"}`}
131
+ ></span>
132
+ </>
133
+ )}
134
+ </div>
134
135
 
135
- <div className="conversation-info">
136
- <div className="conversation-header">
137
- <p className="conversation-name">
138
- {role === "admin" &&
139
- Array.isArray(conversation?.participantDetails)
140
- ? conversation.participantDetails[1]?.firstname
141
- : !Array.isArray(conversation?.participantDetails)
142
- ? conversation?.participantDetails?.firstname
143
- : undefined}
144
- </p>
145
- <span className="conversation-time">
146
- {conversation.lastMessage.status === "deleted"
147
- ? // Show deleted timestamp if message is deleted
148
- new Date(
149
- conversation.lastMessage.updatedAt
150
- ).toLocaleTimeString([], {
151
- hour: "2-digit",
152
- minute: "2-digit",
153
- })
154
- : conversation.lastMessage.status === "edited"
155
- ? // Show updated timestamp if message was edited
156
- new Date(
157
- conversation.lastMessage.updatedAt
158
- ).toLocaleTimeString([], {
159
- hour: "2-digit",
160
- minute: "2-digit",
161
- })
162
- : // Default to created timestamp
163
- new Date(
164
- conversation.lastMessage.createdAt
165
- ).toLocaleTimeString([], {
166
- hour: "2-digit",
167
- minute: "2-digit",
168
- })}
136
+ <div className="flex-1 ml-3">
137
+ <div className="flex justify-between items-center">
138
+ <p className="text-sm font-semibold text-gray-800">
139
+ {conversationName}
140
+ </p>
141
+ <div className="flex items-center gap-2">
142
+ {unreadCount > 0 && (
143
+ <span className="bg-blue-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
144
+ {unreadCount}
145
+ </span>
146
+ )}
147
+ <span className="text-xs text-gray-500">
148
+ {lastMessageTimestamp}
169
149
  </span>
170
150
  </div>
171
- <p className="conversation-message">
172
- {conversation.lastMessage.status === "deleted" ? (
173
- "This message was deleted"
174
- ) : conversation.lastMessage.type !== "system" &&
175
- conversation.lastMessage.message.length > 50 ? (
176
- conversation.lastMessage.message.slice(0, 50) + "..."
177
- ) : conversation.lastMessage.media.length > 0 ? (
178
- <div
179
- style={{ display: "flex", alignItems: "center", gap: "5px" }}
180
- >
181
- <svg
182
- xmlns="http://www.w3.org/2000/svg"
183
- width="18"
184
- height="18"
185
- viewBox="0 0 24 24"
186
- fill="none"
187
- stroke="currentColor"
188
- strokeWidth="2"
189
- strokeLinecap="round"
190
- strokeLinejoin="round"
191
- >
192
- <path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
193
- </svg>
194
- attachment
195
- </div>
196
- ) : (
197
- conversation.lastMessage.message
198
- )}
199
- </p>
151
+ {/* <span className="text-xs text-gray-500">{lastMessageTimestamp}</span> */}
200
152
  </div>
153
+ <p className="text-xs text-gray-600 truncate">
154
+ {conversation.lastMessage?.status === "deleted"
155
+ ? "This message was deleted"
156
+ : conversation.lastMessage?.media?.length > 0
157
+ ? "Attachment"
158
+ : conversation.lastMessage?.message}
159
+ </p>
201
160
  </div>
202
- </>
161
+ </div>
203
162
  );
204
163
  };
205
164
 
@@ -1,56 +1,286 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
-
2
+ import { useEffect, useState } from "react";
3
3
  import { useGetConversations } from "../../hooks/queries/useChatApi";
4
4
  import { useChatContext } from "../../providers/ChatProvider";
5
- import Conversation from "./Conversation";
5
+ import {
6
+ Conversation as ConversationType,
7
+ ParticipantGroup,
8
+ } from "../../types/type";
9
+ import CollapsibleSection from "../common/CollapsibleSection";
10
+ import useChatUIStore from "../../stores/Zustant";
11
+ import VirtualizedChatList from "../common/VirtualizedChatList";
12
+
13
+ type GroupedServiceChats = {
14
+ [serviceId: string]: {
15
+ serviceTitle: string;
16
+ conversations: ConversationType[];
17
+ };
18
+ };
6
19
 
7
20
  const Conversations = () => {
8
- const { userId } = useChatContext();
9
- const { data: conversations } = useGetConversations(userId);
10
- console.log("conversations", conversations);
11
- console.log("userId", userId);
12
- // const { loading, conversations } = useGetConversations();
21
+ const { userId, socket } = useChatContext();
22
+ const { data: participantGroups } = useGetConversations(userId);
23
+ const { searchTerm } = useChatUIStore();
24
+ const [conversations, setConversations] = useState<{
25
+ generalChats: ConversationType[];
26
+ groupedServiceChats: GroupedServiceChats;
27
+ }>({ generalChats: [], groupedServiceChats: {} });
28
+
29
+ // Process fetched data
30
+ useEffect(() => {
31
+ if (!participantGroups) return;
32
+
33
+ const processConversations = (groups: ParticipantGroup[]) => {
34
+ const allGeneralChats: ConversationType[] = [];
35
+ const allServiceChatsMap = new Map<string, GroupedServiceChats[string]>();
36
+
37
+ groups.forEach((group) => {
38
+ if (group.personalConversation) {
39
+ allGeneralChats.push(group.personalConversation);
40
+ }
41
+
42
+ group.serviceConversations.forEach((serviceGroup) => {
43
+ if (!allServiceChatsMap.has(serviceGroup.serviceId)) {
44
+ allServiceChatsMap.set(serviceGroup.serviceId, {
45
+ serviceTitle: serviceGroup.serviceTitle,
46
+ conversations: [],
47
+ });
48
+ }
49
+ allServiceChatsMap
50
+ .get(serviceGroup.serviceId)!
51
+ .conversations.push(...serviceGroup.conversations);
52
+ });
53
+ });
54
+
55
+ const sortConversations = (convos: ConversationType[]) =>
56
+ convos.sort(
57
+ (a, b) =>
58
+ new Date(b.lastMessage?.createdAt || b.updatedAt).getTime() -
59
+ new Date(a.lastMessage?.createdAt || a.updatedAt).getTime()
60
+ );
61
+
62
+ sortConversations(allGeneralChats);
63
+ allServiceChatsMap.forEach((value) =>
64
+ sortConversations(value.conversations)
65
+ );
66
+
67
+ return {
68
+ generalChats: allGeneralChats,
69
+ groupedServiceChats: Object.fromEntries(allServiceChatsMap),
70
+ };
71
+ };
72
+
73
+ setConversations(processConversations(participantGroups));
74
+ }, [participantGroups]);
75
+
76
+ // Real-time update listeners
77
+ useEffect(() => {
78
+ if (!socket) return;
79
+
80
+ const handleMessageReadAck = (data: {
81
+ messageIds: string[];
82
+ chatId: string;
83
+ }) => {
84
+ const { chatId, messageIds } = data;
85
+
86
+ setConversations((prev) => {
87
+ const generalChats = [...prev.generalChats];
88
+ const groupedServiceChats = { ...prev.groupedServiceChats };
89
+
90
+ const updateRead = (convo: ConversationType) => {
91
+ if (convo._id !== chatId) return convo;
92
+ const updatedUnread = (convo.unreadMessageIds || []).filter(
93
+ (id) => !messageIds.includes(id)
94
+ );
95
+ return {
96
+ ...convo,
97
+ unreadMessageIds: updatedUnread,
98
+ unreadMessageCount: updatedUnread.length,
99
+ };
100
+ };
101
+
102
+ const generalIndex = generalChats.findIndex((c) => c._id === chatId);
103
+ if (generalIndex >= 0) {
104
+ generalChats[generalIndex] = updateRead(generalChats[generalIndex]);
105
+ return { generalChats, groupedServiceChats };
106
+ }
107
+
108
+ for (const serviceId in groupedServiceChats) {
109
+ const convos = groupedServiceChats[serviceId].conversations;
110
+ const convoIndex = convos.findIndex((c) => c._id === chatId);
111
+ if (convoIndex >= 0) {
112
+ const updatedConvos = [...convos];
113
+ updatedConvos[convoIndex] = updateRead(updatedConvos[convoIndex]);
114
+ groupedServiceChats[serviceId] = {
115
+ ...groupedServiceChats[serviceId],
116
+ conversations: updatedConvos,
117
+ };
118
+ return { generalChats, groupedServiceChats };
119
+ }
120
+ }
121
+
122
+ return prev;
123
+ });
124
+ };
125
+
126
+ const handleNewMessage = (newMessage: any) => {
127
+ if (!newMessage?.conversationId) return;
128
+
129
+ setConversations((prev) => {
130
+ const generalChats = [...prev.generalChats];
131
+ const groupedServiceChats = { ...prev.groupedServiceChats };
132
+
133
+ const updateConversation = (convo: ConversationType) => ({
134
+ ...convo,
135
+ lastMessage: newMessage,
136
+ updatedAt: new Date().toISOString(),
137
+ unreadMessageIds:
138
+ userId !== newMessage.senderId
139
+ ? [...(convo.unreadMessageIds || []), newMessage._id]
140
+ : convo.unreadMessageIds || [],
141
+ unreadMessageCount:
142
+ userId !== newMessage.senderId
143
+ ? (convo.unreadMessageCount || 0) + 1
144
+ : convo.unreadMessageCount || 0,
145
+ });
146
+
147
+ const generalIndex = generalChats.findIndex(
148
+ (c) => c._id === newMessage.conversationId
149
+ );
150
+ if (generalIndex >= 0) {
151
+ generalChats[generalIndex] = updateConversation(
152
+ generalChats[generalIndex]
153
+ );
154
+ return { generalChats, groupedServiceChats };
155
+ }
156
+
157
+ for (const serviceId in groupedServiceChats) {
158
+ const serviceIndex = groupedServiceChats[
159
+ serviceId
160
+ ].conversations.findIndex((c) => c._id === newMessage.conversationId);
161
+ if (serviceIndex >= 0) {
162
+ const updatedConversations = [
163
+ ...groupedServiceChats[serviceId].conversations,
164
+ ];
165
+ updatedConversations[serviceIndex] = updateConversation(
166
+ updatedConversations[serviceIndex]
167
+ );
168
+ groupedServiceChats[serviceId] = {
169
+ ...groupedServiceChats[serviceId],
170
+ conversations: updatedConversations,
171
+ };
172
+ return { generalChats, groupedServiceChats };
173
+ }
174
+ }
175
+
176
+ generalChats.push({
177
+ _id: newMessage.conversationId,
178
+ participants: [newMessage.senderId, newMessage.receiverId],
179
+ lastMessage: newMessage,
180
+ updatedAt: new Date().toISOString(),
181
+ unreadMessageIds:
182
+ userId !== newMessage.senderId ? [newMessage._id] : [],
183
+ unreadMessageCount: userId !== newMessage.senderId ? 1 : 0,
184
+ type: "personal",
185
+ readReceipts: [],
186
+ createdAt: new Date().toISOString(),
187
+ });
188
+
189
+ return { generalChats, groupedServiceChats };
190
+ });
191
+ };
192
+
193
+ const messageListener = (event: MessageEvent) => {
194
+ try {
195
+ const data = JSON.parse(event.data);
196
+ if (data.event === "newMessage") {
197
+ handleNewMessage(data.data);
198
+ } else if (
199
+ data.event === "messageStatusUpdated" &&
200
+ data.data.status === "read"
201
+ ) {
202
+ handleMessageReadAck({
203
+ messageIds: Array.isArray(data.data.messageId)
204
+ ? data.data.messageId
205
+ : [data.data.messageId],
206
+ chatId: data.data.chatId,
207
+ });
208
+ }
209
+ } catch (e) {
210
+ console.error("Error parsing socket message:", e);
211
+ }
212
+ };
213
+
214
+ socket.addEventListener("message", messageListener);
215
+ return () => socket.removeEventListener("message", messageListener);
216
+ }, [socket, userId]);
217
+
218
+ const isEmpty =
219
+ conversations.generalChats.length === 0 &&
220
+ Object.keys(conversations.groupedServiceChats).length === 0;
221
+
222
+ const lowerSearch = searchTerm?.toLowerCase().trim();
223
+
224
+ const filteredGeneralChats = conversations.generalChats.filter((convo) =>
225
+ convo.participantDetails?.some((p:any) =>
226
+ typeof p === "string"
227
+ ? p.toLowerCase().includes(lowerSearch)
228
+ : p.firstname?.toLowerCase().includes(lowerSearch)
229
+ )
230
+ );
231
+
232
+ const filteredGroupedServiceChats: GroupedServiceChats = Object.fromEntries(
233
+ Object.entries(conversations.groupedServiceChats)
234
+ .map(([serviceId, group]) => {
235
+ const filteredConvos = group.conversations.filter((convo) =>
236
+ convo.participants?.some((p:any) =>
237
+ typeof p === "string"
238
+ ? p.toLowerCase().includes(lowerSearch)
239
+ : p.name?.toLowerCase().includes(lowerSearch)
240
+ )
241
+ );
242
+ return [serviceId, { ...group, conversations: filteredConvos }];
243
+ })
244
+ .filter(([_, group]) => group.conversations.length > 0) // Remove empty groups
245
+ );
246
+
13
247
  return (
14
248
  <div className="chatSidebarConversations">
15
- <h2
16
- className="text-lg font-semibold text-gray-700"
17
- style={{ paddingLeft: "1rem" }}
18
- >
19
- All Messages
20
- </h2>
21
- {(!conversations || conversations.length === 0) && (
249
+ {isEmpty ? (
22
250
  <div className="flex flex-col items-center justify-center p-8 text-center">
23
- <div className="flex h-16 w-16 items-center justify-center rounded-full bg-gray-100 mb-4">
24
- <svg
25
- xmlns="http://www.w3.org/2000/svg"
26
- width="32"
27
- height="32"
28
- viewBox="0 0 24 24"
29
- fill="none"
30
- stroke="currentColor"
31
- strokeWidth="2"
32
- strokeLinecap="round"
33
- strokeLinejoin="round"
34
- className="text-gray-500"
35
- >
36
- <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
37
- </svg>
38
- </div>
39
251
  <h3 className="text-xl font-semibold mb-1">No Conversations</h3>
40
252
  <p className="text-gray-500 text-sm mb-4">
41
- There are no conversations under &quot;All messages&quot;
253
+ You have no messages yet.
42
254
  </p>
43
255
  </div>
256
+ ) : (
257
+ <>
258
+ {filteredGeneralChats.length > 0 && (
259
+ <CollapsibleSection title="General Chats">
260
+ <VirtualizedChatList conversations={filteredGeneralChats} />
261
+ </CollapsibleSection>
262
+ )}
263
+
264
+ {Object.entries(filteredGroupedServiceChats).length > 0 && (
265
+ <CollapsibleSection title="Service Chats">
266
+ {Object.entries(filteredGroupedServiceChats).map(
267
+ ([
268
+ serviceId,
269
+ { serviceTitle, conversations: serviceConvos },
270
+ ]) => (
271
+ <CollapsibleSection
272
+ key={serviceId}
273
+ title={serviceTitle}
274
+ defaultOpen={false}
275
+ >
276
+ <VirtualizedChatList conversations={serviceConvos} />
277
+ </CollapsibleSection>
278
+ )
279
+ )}
280
+ </CollapsibleSection>
281
+ )}
282
+ </>
44
283
  )}
45
- {conversations?.map((conversation: any, idx: any) => (
46
- <Conversation
47
- key={conversation._id}
48
- conversation={conversation}
49
- lastIdx={idx === conversations.length - 1}
50
- />
51
- ))}
52
-
53
- {/* {loading ? <span className='loading loading-spinner mx-auto'></span> : null} */}
54
284
  </div>
55
285
  );
56
286
  };