@pubuduth-aplicy/chat-ui 2.2.14 → 2.2.15
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/package.json
CHANGED
|
@@ -39,7 +39,7 @@ const MessageInput = () => {
|
|
|
39
39
|
const apiClient = getApiClient();
|
|
40
40
|
const { role } = getChatConfig();
|
|
41
41
|
const { socket, sendMessage, userId } = useChatContext();
|
|
42
|
-
const { selectedConversation, setMessages } = useChatUIStore();
|
|
42
|
+
const { selectedConversation, setMessages, setLastSentMessage } = useChatUIStore();
|
|
43
43
|
|
|
44
44
|
const [message, setMessage] = useState("");
|
|
45
45
|
const [message1, setMessage1] = useState("");
|
|
@@ -47,8 +47,6 @@ const MessageInput = () => {
|
|
|
47
47
|
|
|
48
48
|
const [typingUser, setTypingUser] = useState<string | null>(null);
|
|
49
49
|
const [isSending, setIsSending] = useState(false);
|
|
50
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
51
|
-
const [isTyping, setIsTyping] = useState(false);
|
|
52
50
|
const [attachments, setAttachments] = useState<Attachment[]>([]);
|
|
53
51
|
const [showAttachmentOptions, setShowAttachmentOptions] = useState(false);
|
|
54
52
|
|
|
@@ -474,6 +472,8 @@ const MessageInput = () => {
|
|
|
474
472
|
},
|
|
475
473
|
{
|
|
476
474
|
onSuccess: (data) => {
|
|
475
|
+
const confirmedMessage = data[1];
|
|
476
|
+
|
|
477
477
|
setMessages((prev) => {
|
|
478
478
|
const filtered = prev.filter(
|
|
479
479
|
(msg) => msg._id !== tempMessageId
|
|
@@ -481,20 +481,32 @@ const MessageInput = () => {
|
|
|
481
481
|
return [
|
|
482
482
|
...filtered,
|
|
483
483
|
{
|
|
484
|
-
...
|
|
484
|
+
...confirmedMessage,
|
|
485
485
|
isUploading: false,
|
|
486
486
|
isOptimistic: false,
|
|
487
487
|
},
|
|
488
488
|
];
|
|
489
489
|
});
|
|
490
490
|
|
|
491
|
+
// Update the sidebar last message for the sender immediately
|
|
492
|
+
setLastSentMessage({
|
|
493
|
+
_id: confirmedMessage._id,
|
|
494
|
+
conversationId: selectedConversation?._id ?? "",
|
|
495
|
+
message: message1,
|
|
496
|
+
senderId: userId,
|
|
497
|
+
media: successfulUploads,
|
|
498
|
+
status: confirmedMessage.status ?? "sent",
|
|
499
|
+
createdAt: confirmedMessage.createdAt ?? new Date().toISOString(),
|
|
500
|
+
updatedAt: confirmedMessage.updatedAt ?? new Date().toISOString(),
|
|
501
|
+
});
|
|
502
|
+
|
|
491
503
|
// Send message via WebSocket
|
|
492
504
|
sendMessage({
|
|
493
505
|
event: "sendMessage",
|
|
494
506
|
data: {
|
|
495
507
|
chatId: selectedConversation?._id,
|
|
496
508
|
message: message1,
|
|
497
|
-
messageId:
|
|
509
|
+
messageId: confirmedMessage._id,
|
|
498
510
|
attachments: successfulUploads,
|
|
499
511
|
senderId: userId,
|
|
500
512
|
receiverId: otherParticipant._id,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
3
|
import { useGetConversations } from "../../hooks/queries/useChatApi";
|
|
4
4
|
import { useChatContext } from "../../providers/ChatProvider";
|
|
5
5
|
import {
|
|
@@ -22,7 +22,7 @@ type TabType = "personal" | "service";
|
|
|
22
22
|
const Conversations = () => {
|
|
23
23
|
const { userId, socket } = useChatContext();
|
|
24
24
|
const { data: participantGroups } = useGetConversations(userId);
|
|
25
|
-
const { searchTerm } = useChatUIStore();
|
|
25
|
+
const { searchTerm, lastSentMessage, setLastSentMessage } = useChatUIStore();
|
|
26
26
|
const [activeTab, setActiveTab] = useState<TabType>("personal");
|
|
27
27
|
const [conversations, setConversations] = useState<{
|
|
28
28
|
personalChats: ConversationType[];
|
|
@@ -77,6 +77,68 @@ const Conversations = () => {
|
|
|
77
77
|
setConversations(processConversations(participantGroups));
|
|
78
78
|
}, [participantGroups]);
|
|
79
79
|
|
|
80
|
+
// ── Hoisted to component scope so both the socket listener AND the
|
|
81
|
+
// lastSentMessage effect can call it ─────────────────────────────
|
|
82
|
+
const handleNewMessage = useCallback((newMessage: any) => {
|
|
83
|
+
if (!newMessage?.conversationId) return;
|
|
84
|
+
|
|
85
|
+
setConversations((prev) => {
|
|
86
|
+
const personalChats = [...prev.personalChats];
|
|
87
|
+
const groupedServiceChats = { ...prev.groupedServiceChats };
|
|
88
|
+
|
|
89
|
+
const updateConversation = (convo: ConversationType) => ({
|
|
90
|
+
...convo,
|
|
91
|
+
lastMessage: newMessage,
|
|
92
|
+
updatedAt: new Date().toISOString(),
|
|
93
|
+
unreadMessageIds:
|
|
94
|
+
userId !== newMessage.senderId
|
|
95
|
+
? [...(convo.unreadMessageIds || []), newMessage._id]
|
|
96
|
+
: convo.unreadMessageIds || [],
|
|
97
|
+
unreadMessageCount:
|
|
98
|
+
userId !== newMessage.senderId
|
|
99
|
+
? (convo.unreadMessageCount || 0) + 1
|
|
100
|
+
: convo.unreadMessageCount || 0,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const personalIndex = personalChats.findIndex(
|
|
104
|
+
(c) => c._id === newMessage.conversationId
|
|
105
|
+
);
|
|
106
|
+
if (personalIndex >= 0) {
|
|
107
|
+
personalChats[personalIndex] = updateConversation(personalChats[personalIndex]);
|
|
108
|
+
return { personalChats, groupedServiceChats };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const serviceId in groupedServiceChats) {
|
|
112
|
+
const serviceIndex = groupedServiceChats[serviceId].conversations.findIndex(
|
|
113
|
+
(c) => c._id === newMessage.conversationId
|
|
114
|
+
);
|
|
115
|
+
if (serviceIndex >= 0) {
|
|
116
|
+
const updatedConversations = [...groupedServiceChats[serviceId].conversations];
|
|
117
|
+
updatedConversations[serviceIndex] = updateConversation(updatedConversations[serviceIndex]);
|
|
118
|
+
groupedServiceChats[serviceId] = {
|
|
119
|
+
...groupedServiceChats[serviceId],
|
|
120
|
+
conversations: updatedConversations,
|
|
121
|
+
};
|
|
122
|
+
return { personalChats, groupedServiceChats };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fallback: brand-new conversation not yet in local state
|
|
127
|
+
personalChats.push({
|
|
128
|
+
_id: newMessage.conversationId,
|
|
129
|
+
participants: [newMessage.senderId, newMessage.receiverId],
|
|
130
|
+
lastMessage: newMessage,
|
|
131
|
+
updatedAt: new Date().toISOString(),
|
|
132
|
+
unreadMessageIds: userId !== newMessage.senderId ? [newMessage._id] : [],
|
|
133
|
+
unreadMessageCount: userId !== newMessage.senderId ? 1 : 0,
|
|
134
|
+
type: "personal",
|
|
135
|
+
createdAt: new Date().toISOString(),
|
|
136
|
+
} as any);
|
|
137
|
+
|
|
138
|
+
return { personalChats, groupedServiceChats };
|
|
139
|
+
});
|
|
140
|
+
}, [userId]);
|
|
141
|
+
|
|
80
142
|
// Real-time update listeners
|
|
81
143
|
useEffect(() => {
|
|
82
144
|
if (!socket) return;
|
|
@@ -129,86 +191,14 @@ const Conversations = () => {
|
|
|
129
191
|
});
|
|
130
192
|
};
|
|
131
193
|
|
|
132
|
-
const handleNewMessage = (newMessage: any) => {
|
|
133
|
-
if (!newMessage?.conversationId) return;
|
|
134
|
-
|
|
135
|
-
setConversations((prev) => {
|
|
136
|
-
const personalChats = [...prev.personalChats];
|
|
137
|
-
const groupedServiceChats = { ...prev.groupedServiceChats };
|
|
138
|
-
|
|
139
|
-
const updateConversation = (convo: ConversationType) => ({
|
|
140
|
-
...convo,
|
|
141
|
-
lastMessage: newMessage,
|
|
142
|
-
updatedAt: new Date().toISOString(),
|
|
143
|
-
unreadMessageIds:
|
|
144
|
-
userId !== newMessage.senderId
|
|
145
|
-
? [...(convo.unreadMessageIds || []), newMessage._id]
|
|
146
|
-
: convo.unreadMessageIds || [],
|
|
147
|
-
unreadMessageCount:
|
|
148
|
-
userId !== newMessage.senderId
|
|
149
|
-
? (convo.unreadMessageCount || 0) + 1
|
|
150
|
-
: convo.unreadMessageCount || 0,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const personalIndex = personalChats.findIndex(
|
|
154
|
-
(c) => c._id === newMessage.conversationId
|
|
155
|
-
);
|
|
156
|
-
if (personalIndex >= 0) {
|
|
157
|
-
personalChats[personalIndex] = updateConversation(
|
|
158
|
-
personalChats[personalIndex]
|
|
159
|
-
);
|
|
160
|
-
return { personalChats, groupedServiceChats };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
for (const serviceId in groupedServiceChats) {
|
|
164
|
-
const serviceIndex = groupedServiceChats[
|
|
165
|
-
serviceId
|
|
166
|
-
].conversations.findIndex((c) => c._id === newMessage.conversationId);
|
|
167
|
-
if (serviceIndex >= 0) {
|
|
168
|
-
const updatedConversations = [
|
|
169
|
-
...groupedServiceChats[serviceId].conversations,
|
|
170
|
-
];
|
|
171
|
-
updatedConversations[serviceIndex] = updateConversation(
|
|
172
|
-
updatedConversations[serviceIndex]
|
|
173
|
-
);
|
|
174
|
-
groupedServiceChats[serviceId] = {
|
|
175
|
-
...groupedServiceChats[serviceId],
|
|
176
|
-
conversations: updatedConversations,
|
|
177
|
-
};
|
|
178
|
-
return { personalChats, groupedServiceChats };
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
personalChats.push({
|
|
183
|
-
_id: newMessage.conversationId,
|
|
184
|
-
participants: [newMessage.senderId, newMessage.receiverId],
|
|
185
|
-
lastMessage: newMessage,
|
|
186
|
-
updatedAt: new Date().toISOString(),
|
|
187
|
-
unreadMessageIds:
|
|
188
|
-
userId !== newMessage.senderId ? [newMessage._id] : [],
|
|
189
|
-
unreadMessageCount: userId !== newMessage.senderId ? 1 : 0,
|
|
190
|
-
type: "personal",
|
|
191
|
-
readReceipts: [],
|
|
192
|
-
createdAt: new Date().toISOString(),
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
return { personalChats, groupedServiceChats };
|
|
196
|
-
});
|
|
197
|
-
};
|
|
198
|
-
|
|
199
194
|
const messageListener = (event: MessageEvent) => {
|
|
200
195
|
try {
|
|
201
196
|
const parsed = JSON.parse(event.data);
|
|
202
197
|
|
|
203
198
|
if (parsed.event === "newMessage") {
|
|
204
199
|
// Actual message is nested at parsed.data.data — same structure as Messages.tsx
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
} else if (
|
|
208
|
-
parsed.event === "messageStatusUpdated" &&
|
|
209
|
-
parsed.data?.status === "read"
|
|
210
|
-
) {
|
|
211
|
-
// messageId can come as a single id or an array depending on backend
|
|
200
|
+
handleNewMessage(parsed?.data?.data);
|
|
201
|
+
} else if (parsed.event === "messageStatusUpdated" && parsed.data?.status === "read") {
|
|
212
202
|
const rawId = parsed.data.messageId ?? parsed.data.messageIds;
|
|
213
203
|
handleMessageReadAck({
|
|
214
204
|
messageIds: Array.isArray(rawId) ? rawId : [rawId].filter(Boolean),
|
|
@@ -222,7 +212,14 @@ const Conversations = () => {
|
|
|
222
212
|
|
|
223
213
|
socket.addEventListener("message", messageListener);
|
|
224
214
|
return () => socket.removeEventListener("message", messageListener);
|
|
225
|
-
}, [socket,
|
|
215
|
+
}, [socket, handleNewMessage]);
|
|
216
|
+
|
|
217
|
+
// React to messages sent by the current user (no server echo needed)
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
if (!lastSentMessage) return;
|
|
220
|
+
handleNewMessage(lastSentMessage);
|
|
221
|
+
setLastSentMessage(null);
|
|
222
|
+
}, [lastSentMessage, handleNewMessage, setLastSentMessage]);
|
|
226
223
|
|
|
227
224
|
// const isEmpty =
|
|
228
225
|
// activeTab === "personal"
|
|
@@ -234,11 +231,12 @@ const Conversations = () => {
|
|
|
234
231
|
// Filter personal chats
|
|
235
232
|
const filteredPersonalChats = !lowerSearch
|
|
236
233
|
? conversations.personalChats
|
|
237
|
-
: conversations.personalChats.filter((convo) =>
|
|
238
|
-
convo.participantDetails
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
234
|
+
: conversations.personalChats.filter((convo) => {
|
|
235
|
+
const details = Array.isArray(convo.participantDetails)
|
|
236
|
+
? convo.participantDetails
|
|
237
|
+
: [convo.participantDetails].filter(Boolean);
|
|
238
|
+
return details.some((p: any) => p?.name?.toLowerCase().includes(lowerSearch));
|
|
239
|
+
});
|
|
242
240
|
|
|
243
241
|
// Filter service chats
|
|
244
242
|
const filteredGroupedServiceChats: GroupedServiceChats = !lowerSearch
|
|
@@ -246,17 +244,21 @@ const Conversations = () => {
|
|
|
246
244
|
: Object.fromEntries(
|
|
247
245
|
Object.entries(conversations.groupedServiceChats)
|
|
248
246
|
.map(([serviceId, group]) => {
|
|
247
|
+
const details = (convo: ConversationType) => {
|
|
248
|
+
const d = Array.isArray(convo.participantDetails)
|
|
249
|
+
? convo.participantDetails
|
|
250
|
+
: [convo.participantDetails].filter(Boolean);
|
|
251
|
+
return d.some((p: any) => p?.name?.toLowerCase().includes(lowerSearch));
|
|
252
|
+
};
|
|
249
253
|
const filteredConvos = group.conversations.filter(
|
|
250
254
|
(convo) =>
|
|
251
|
-
convo
|
|
252
|
-
p?.name?.toLowerCase().includes(lowerSearch)
|
|
253
|
-
) ||
|
|
255
|
+
details(convo) ||
|
|
254
256
|
group.serviceTitle?.toLowerCase().includes(lowerSearch) ||
|
|
255
257
|
convo.serviceTitle?.toLowerCase().includes(lowerSearch)
|
|
256
258
|
);
|
|
257
|
-
return [serviceId, { ...group, conversations: filteredConvos }];
|
|
259
|
+
return [serviceId, { ...group, conversations: filteredConvos }] as [string, GroupedServiceChats[string]];
|
|
258
260
|
})
|
|
259
|
-
.filter(([
|
|
261
|
+
.filter(([, group]) => group.conversations.length > 0)
|
|
260
262
|
);
|
|
261
263
|
|
|
262
264
|
// Improved empty state logic
|
package/src/stores/Zustant.ts
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
import { FileType } from "../components/common/FilePreview";
|
|
3
3
|
import { create } from "zustand";
|
|
4
4
|
|
|
5
|
+
/** Minimal shape needed to update the sidebar after a send */
|
|
6
|
+
export interface SentMessageSignal {
|
|
7
|
+
_id: string;
|
|
8
|
+
conversationId: string;
|
|
9
|
+
message: string;
|
|
10
|
+
senderId: string;
|
|
11
|
+
media?: any[];
|
|
12
|
+
status: string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
interface ChatUIState {
|
|
6
18
|
isChatOpen: boolean;
|
|
7
19
|
unreadCount: number;
|
|
@@ -51,6 +63,9 @@ interface ChatUIState {
|
|
|
51
63
|
setOnlineUsers: (users: string[]) => void;
|
|
52
64
|
searchTerm: string;
|
|
53
65
|
setSearchTerm: (searchTerm: string) => void;
|
|
66
|
+
/** Signal published by MessageInput after a successful send so the sidebar can update */
|
|
67
|
+
lastSentMessage: SentMessageSignal | null;
|
|
68
|
+
setLastSentMessage: (msg: SentMessageSignal | null) => void;
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
const useChatUIStore = create<ChatUIState>((set) => ({
|
|
@@ -74,6 +89,8 @@ const useChatUIStore = create<ChatUIState>((set) => ({
|
|
|
74
89
|
setOnlineUsers: (users) => set({ onlineUsers: users }),
|
|
75
90
|
searchTerm: "",
|
|
76
91
|
setSearchTerm: (searchTerm) => set({ searchTerm }),
|
|
92
|
+
lastSentMessage: null,
|
|
93
|
+
setLastSentMessage: (msg) => set({ lastSentMessage: msg }),
|
|
77
94
|
}));
|
|
78
95
|
|
|
79
96
|
export default useChatUIStore;
|