@pubuduth-aplicy/chat-ui 2.1.78 → 2.1.80

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubuduth-aplicy/chat-ui",
3
- "version": "2.1.78",
3
+ "version": "2.1.80",
4
4
  "description": "This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -1,7 +1,7 @@
1
1
  // config.ts
2
2
  export interface ChatConfig {
3
3
  apiUrl: string;
4
- role?: string;
4
+ role: string;
5
5
  cdnUrl?: string;
6
6
  webSocketUrl: string;
7
7
  }
@@ -7,6 +7,7 @@ import { FilePreview, FileType } from "../common/FilePreview";
7
7
  import { getApiClient } from "../../lib/api/apiClient";
8
8
  import { MessageStatus } from "../../types/type";
9
9
  import { Path } from "../../lib/api/endpoint";
10
+ import { getChatConfig } from "../../Chat.config";
10
11
 
11
12
  const MAX_FILE_SIZE_MB = 5;
12
13
  const MAX_FILE_COUNT = 5;
@@ -37,6 +38,7 @@ interface Attachment {
37
38
 
38
39
  const MessageInput = () => {
39
40
  const apiClient = getApiClient();
41
+ const {role} = getChatConfig();
40
42
  const { socket, sendMessage, userId } = useChatContext();
41
43
  const { selectedConversation, setMessages } = useChatUIStore();
42
44
  const [message, setMessage] = useState("");
@@ -349,6 +351,8 @@ const MessageInput = () => {
349
351
  selectedConversation?.type === "service"
350
352
  ? selectedConversation?.serviceId
351
353
  : undefined,
354
+ senderRole: role,
355
+ receiverRole: role === 'customer' ? 'provider' : 'customer',
352
356
  },
353
357
  {
354
358
  onSuccess: (data) => {
@@ -17,28 +17,29 @@ type GroupedServiceChats = {
17
17
  };
18
18
  };
19
19
 
20
+ type TabType = "personal" | "service";
21
+
20
22
  const Conversations = () => {
21
23
  const { userId, socket } = useChatContext();
22
24
  const { data: participantGroups } = useGetConversations(userId);
23
25
  const { searchTerm } = useChatUIStore();
26
+ const [activeTab, setActiveTab] = useState<TabType>("personal");
24
27
  const [conversations, setConversations] = useState<{
25
- generalChats: ConversationType[];
28
+ personalChats: ConversationType[];
26
29
  groupedServiceChats: GroupedServiceChats;
27
- }>({ generalChats: [], groupedServiceChats: {} });
30
+ }>({ personalChats: [], groupedServiceChats: {} });
28
31
 
29
32
  // Process fetched data
30
33
  useEffect(() => {
31
34
  if (!participantGroups) return;
32
35
 
33
36
  const processConversations = (groups: ParticipantGroup[]) => {
34
- const allGeneralChats: ConversationType[] = [];
37
+ const allPersonalChats: ConversationType[] = [];
35
38
  const allServiceChatsMap = new Map<string, GroupedServiceChats[string]>();
36
39
 
37
40
  groups.forEach((group) => {
38
- // console.log("group", group.personalConversation);
39
-
40
41
  if (group.personalConversation) {
41
- allGeneralChats.push(group.personalConversation);
42
+ allPersonalChats.push(group.personalConversation);
42
43
  }
43
44
 
44
45
  group.serviceConversations.forEach((serviceGroup) => {
@@ -61,13 +62,13 @@ const Conversations = () => {
61
62
  new Date(a.lastMessage?.createdAt || a.updatedAt).getTime()
62
63
  );
63
64
 
64
- sortConversations(allGeneralChats);
65
+ sortConversations(allPersonalChats);
65
66
  allServiceChatsMap.forEach((value) =>
66
67
  sortConversations(value.conversations)
67
68
  );
68
69
 
69
70
  return {
70
- generalChats: allGeneralChats,
71
+ personalChats: allPersonalChats,
71
72
  groupedServiceChats: Object.fromEntries(allServiceChatsMap),
72
73
  };
73
74
  };
@@ -86,7 +87,7 @@ const Conversations = () => {
86
87
  const { chatId, messageIds } = data;
87
88
 
88
89
  setConversations((prev) => {
89
- const generalChats = [...prev.generalChats];
90
+ const personalChats = [...prev.personalChats];
90
91
  const groupedServiceChats = { ...prev.groupedServiceChats };
91
92
 
92
93
  const updateRead = (convo: ConversationType) => {
@@ -101,10 +102,10 @@ const Conversations = () => {
101
102
  };
102
103
  };
103
104
 
104
- const generalIndex = generalChats.findIndex((c) => c._id === chatId);
105
- if (generalIndex >= 0) {
106
- generalChats[generalIndex] = updateRead(generalChats[generalIndex]);
107
- return { generalChats, groupedServiceChats };
105
+ const personalIndex = personalChats.findIndex((c) => c._id === chatId);
106
+ if (personalIndex >= 0) {
107
+ personalChats[personalIndex] = updateRead(personalChats[personalIndex]);
108
+ return { personalChats, groupedServiceChats };
108
109
  }
109
110
 
110
111
  for (const serviceId in groupedServiceChats) {
@@ -117,7 +118,7 @@ const Conversations = () => {
117
118
  ...groupedServiceChats[serviceId],
118
119
  conversations: updatedConvos,
119
120
  };
120
- return { generalChats, groupedServiceChats };
121
+ return { personalChats, groupedServiceChats };
121
122
  }
122
123
  }
123
124
 
@@ -129,7 +130,7 @@ const Conversations = () => {
129
130
  if (!newMessage?.conversationId) return;
130
131
 
131
132
  setConversations((prev) => {
132
- const generalChats = [...prev.generalChats];
133
+ const personalChats = [...prev.personalChats];
133
134
  const groupedServiceChats = { ...prev.groupedServiceChats };
134
135
 
135
136
  const updateConversation = (convo: ConversationType) => ({
@@ -146,14 +147,14 @@ const Conversations = () => {
146
147
  : convo.unreadMessageCount || 0,
147
148
  });
148
149
 
149
- const generalIndex = generalChats.findIndex(
150
+ const personalIndex = personalChats.findIndex(
150
151
  (c) => c._id === newMessage.conversationId
151
152
  );
152
- if (generalIndex >= 0) {
153
- generalChats[generalIndex] = updateConversation(
154
- generalChats[generalIndex]
153
+ if (personalIndex >= 0) {
154
+ personalChats[personalIndex] = updateConversation(
155
+ personalChats[personalIndex]
155
156
  );
156
- return { generalChats, groupedServiceChats };
157
+ return { personalChats, groupedServiceChats };
157
158
  }
158
159
 
159
160
  for (const serviceId in groupedServiceChats) {
@@ -171,11 +172,11 @@ const Conversations = () => {
171
172
  ...groupedServiceChats[serviceId],
172
173
  conversations: updatedConversations,
173
174
  };
174
- return { generalChats, groupedServiceChats };
175
+ return { personalChats, groupedServiceChats };
175
176
  }
176
177
  }
177
178
 
178
- generalChats.push({
179
+ personalChats.push({
179
180
  _id: newMessage.conversationId,
180
181
  participants: [newMessage.senderId, newMessage.receiverId],
181
182
  lastMessage: newMessage,
@@ -188,7 +189,7 @@ const Conversations = () => {
188
189
  createdAt: new Date().toISOString(),
189
190
  });
190
191
 
191
- return { generalChats, groupedServiceChats };
192
+ return { personalChats, groupedServiceChats };
192
193
  });
193
194
  };
194
195
 
@@ -218,13 +219,14 @@ const Conversations = () => {
218
219
  }, [socket, userId]);
219
220
 
220
221
  const isEmpty =
221
- conversations.generalChats.length === 0 &&
222
- Object.keys(conversations.groupedServiceChats).length === 0;
222
+ activeTab === "personal"
223
+ ? conversations.personalChats.length === 0
224
+ : Object.keys(conversations.groupedServiceChats).length === 0;
223
225
 
224
226
  const lowerSearch = searchTerm?.toLowerCase().trim();
225
227
 
226
- const filteredGeneralChats = conversations.generalChats.filter((convo) =>
227
- convo.participantDetails?.some((p:any) =>
228
+ const filteredPersonalChats = conversations.personalChats.filter((convo) =>
229
+ convo.participantDetails?.some((p: any) =>
228
230
  typeof p === "string"
229
231
  ? p.toLowerCase().includes(lowerSearch)
230
232
  : p.name?.toLowerCase().includes(lowerSearch)
@@ -235,7 +237,7 @@ const Conversations = () => {
235
237
  Object.entries(conversations.groupedServiceChats)
236
238
  .map(([serviceId, group]) => {
237
239
  const filteredConvos = group.conversations.filter((convo) =>
238
- convo.participants?.some((p:any) =>
240
+ convo.participants?.some((p: any) =>
239
241
  typeof p === "string"
240
242
  ? p.toLowerCase().includes(lowerSearch)
241
243
  : p.name?.toLowerCase().includes(lowerSearch)
@@ -246,48 +248,90 @@ const Conversations = () => {
246
248
  .filter(([_, group]) => group.conversations.length > 0) // Remove empty groups
247
249
  );
248
250
 
249
- // console.log("filteredGeneralChats",filteredGeneralChats);
250
- // console.log("filteredGroupedServiceChats");
251
+ // Calculate unread counts for tabs
252
+ const personalUnreadCount = conversations.personalChats.reduce(
253
+ (total, convo) => total + (convo.unreadMessageCount || 0),
254
+ 0
255
+ );
256
+
257
+ const serviceUnreadCount = Object.values(conversations.groupedServiceChats)
258
+ .flatMap(group => group.conversations)
259
+ .reduce((total, convo) => total + (convo.unreadMessageCount || 0), 0);
251
260
 
252
261
  return (
253
262
  <div className="chatSidebarConversations">
254
- {isEmpty ? (
255
- <div className="flex flex-col items-center justify-center p-8 text-center">
256
- <h3 className="text-xl font-semibold mb-1">No Conversations</h3>
257
- <p className="text-gray-500 text-sm mb-4">
258
- You have no messages yet.
259
- </p>
260
- </div>
261
- ) : (
262
- <>
263
- {filteredGeneralChats.length > 0 && (
264
- <CollapsibleSection title="General Chats">
265
- <VirtualizedChatList conversations={filteredGeneralChats} />
266
- </CollapsibleSection>
263
+ {/* Tab Navigation */}
264
+ <div className="flex border-b border-gray-200">
265
+ <button
266
+ className={`flex-1 py-3 px-4 text-sm font-medium text-center border-b-2 transition-colors ${
267
+ activeTab === "personal"
268
+ ? "border-[#317490] message-text bg-blue-50"
269
+ : "border-transparent text-gray-500 hover:text-gray-700 hover:bg-gray-50"
270
+ }`}
271
+ onClick={() => setActiveTab("personal")}
272
+ >
273
+ Personal
274
+ {personalUnreadCount > 0 && (
275
+ <span className="ml-2 bg-blue-500 text-white text-xs rounded-full px-2 py-1 min-w-5 inline-flex justify-center">
276
+ {personalUnreadCount}
277
+ </span>
267
278
  )}
268
-
269
- {Object.entries(filteredGroupedServiceChats).length > 0 && (
270
- <CollapsibleSection title="Service Chats">
271
- {Object.entries(filteredGroupedServiceChats).map(
272
- ([
273
- serviceId,
274
- { serviceTitle, conversations: serviceConvos },
275
- ]) => (
276
- <CollapsibleSection
277
- key={serviceId}
278
- title={serviceTitle}
279
- defaultOpen={false}
280
- >
281
- <VirtualizedChatList conversations={serviceConvos} />
282
- </CollapsibleSection>
283
- )
284
- )}
285
- </CollapsibleSection>
279
+ </button>
280
+ <button
281
+ className={`flex-1 py-3 px-4 text-sm font-medium text-center border-b-2 transition-colors ${
282
+ activeTab === "service"
283
+ ? "border-blue-500 text-blue-600 bg-blue-50"
284
+ : "border-transparent text-gray-500 hover:text-gray-700 hover:bg-gray-50"
285
+ }`}
286
+ onClick={() => setActiveTab("service")}
287
+ >
288
+ Service
289
+ {serviceUnreadCount > 0 && (
290
+ <span className="ml-2 bg-blue-500 text-white text-xs rounded-full px-2 py-1 min-w-5 inline-flex justify-center">
291
+ {serviceUnreadCount}
292
+ </span>
286
293
  )}
287
- </>
288
- )}
294
+ </button>
295
+ </div>
296
+
297
+ {/* Tab Content */}
298
+ <div className="flex-1 overflow-auto">
299
+ {isEmpty ? (
300
+ <div className="flex flex-col items-center justify-center p-8 text-center">
301
+ <h3 className="text-xl font-semibold mb-1">No Conversations</h3>
302
+ <p className="text-gray-500 text-sm mb-4">
303
+ {activeTab === "personal"
304
+ ? "You have no personal messages yet."
305
+ : "You have no service messages yet."}
306
+ </p>
307
+ </div>
308
+ ) : (
309
+ <>
310
+ {activeTab === "personal" && filteredPersonalChats.length > 0 && (
311
+ <VirtualizedChatList conversations={filteredPersonalChats} />
312
+ )}
313
+
314
+ {activeTab === "service" &&
315
+ Object.entries(filteredGroupedServiceChats).length > 0 && (
316
+ <div className="p-2">
317
+ {Object.entries(filteredGroupedServiceChats).map(
318
+ ([serviceId, { serviceTitle, conversations: serviceConvos }]) => (
319
+ <CollapsibleSection
320
+ key={serviceId}
321
+ title={serviceTitle}
322
+ defaultOpen={false}
323
+ >
324
+ <VirtualizedChatList conversations={serviceConvos} />
325
+ </CollapsibleSection>
326
+ )
327
+ )}
328
+ </div>
329
+ )}
330
+ </>
331
+ )}
332
+ </div>
289
333
  </div>
290
334
  );
291
335
  };
292
336
 
293
- export default Conversations;
337
+ export default Conversations;
@@ -41,7 +41,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
41
41
  const reconnectAttempts = useRef(0);
42
42
  const maxReconnectAttempts = 5;
43
43
  const reconnectInterval = 5000; // 5 seconds
44
- const { webSocketUrl } = getChatConfig();
44
+ const { webSocketUrl,role } = getChatConfig();
45
45
 
46
46
  const connectWebSocket = useCallback(() => {
47
47
  // console.log("🔌 Creating new WebSocket connection...");
@@ -49,7 +49,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
49
49
  // Convert HTTP URL to WebSocket URL
50
50
  const wsUrl = webSocketUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:");
51
51
  const socketInstance = new WebSocket(
52
- `${wsUrl}?userId=${encodeURIComponent(userId)}`
52
+ `${wsUrl}?userId=${encodeURIComponent(userId)}&role=${encodeURIComponent(role)}`
53
53
  );
54
54
 
55
55
  socketInstance.onopen = () => {
@@ -62,6 +62,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
62
62
  JSON.stringify({
63
63
  event: "handshake",
64
64
  userId: userId,
65
+ role: role,
65
66
  })
66
67
  );
67
68
  };
@@ -12,6 +12,8 @@ export const sendMessage = async (params: {
12
12
  serviceTitle?: string;
13
13
  type?: "personal" | "service";
14
14
  serviceId?: string;
15
+ senderRole?: string;
16
+ receiverRole?: string;
15
17
  }) => {
16
18
  const {
17
19
  receiverId,
@@ -22,6 +24,8 @@ export const sendMessage = async (params: {
22
24
  serviceTitle,
23
25
  type,
24
26
  serviceId,
27
+ senderRole,
28
+ receiverRole,
25
29
  } = params;
26
30
  const apiClient = getApiClient();
27
31
 
@@ -34,7 +38,9 @@ export const sendMessage = async (params: {
34
38
  serviceTitle,
35
39
  type,
36
40
  serviceId,
37
- messageType: "user"
41
+ messageType: "user",
42
+ senderRole,
43
+ receiverRole
38
44
  }
39
45
  );
40
46
  return response.data;
@@ -12,7 +12,7 @@ export const getAllConversationData = async (userid: string) => {
12
12
 
13
13
  const endpoint = role === 'admin'
14
14
  ? `${Path.getConversationListByAdmin}`
15
- : `${Path.getconversation}/${userid}`;
15
+ : `${Path.getconversation}/${userid}/${role}`;
16
16
 
17
17
  const res = await apiClient.get<ApiResponse>(endpoint);
18
18
  if (res.data) {