@pubuduth-aplicy/chat-ui 2.1.71 → 2.1.73
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 +1 -1
- package/src/components/Chat.tsx +30 -17
- package/src/components/messages/Message.tsx +44 -44
- package/src/components/messages/MessageContainer.tsx +89 -52
- package/src/components/messages/MessageInput.tsx +12 -10
- package/src/components/messages/Messages.tsx +46 -24
- package/src/components/sidebar/Conversation.tsx +103 -49
- package/src/components/sidebar/Conversations.tsx +6 -1
- package/src/providers/ChatProvider.tsx +46 -26
- package/src/stores/Zustant.ts +19 -17
- package/src/style/style.css +32 -24
- package/src/types/type.ts +4 -4
package/package.json
CHANGED
package/src/components/Chat.tsx
CHANGED
|
@@ -5,35 +5,48 @@ import { Sidebar } from "./sidebar/Sidebar";
|
|
|
5
5
|
import { useChatContext } from "../providers/ChatProvider";
|
|
6
6
|
|
|
7
7
|
export const Chat = () => {
|
|
8
|
-
const {
|
|
9
|
-
|
|
10
|
-
const { socket } = useChatContext();
|
|
8
|
+
const { setMessages, updateMessageStatus } = useChatUIStore();
|
|
9
|
+
|
|
10
|
+
const { socket, sendMessage } = useChatContext();
|
|
11
11
|
|
|
12
12
|
useEffect(() => {
|
|
13
13
|
if (!socket) return;
|
|
14
14
|
|
|
15
15
|
const handleMessage = (event: MessageEvent) => {
|
|
16
16
|
try {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
const parsed = JSON.parse(event.data);
|
|
18
|
+
console.log("Parsed WebSocket message:", parsed);
|
|
19
|
+
|
|
20
|
+
if (parsed.event === "newMessage") {
|
|
21
|
+
const message = parsed.data;
|
|
22
|
+
console.log(
|
|
23
|
+
"📨 Message received at:",
|
|
24
|
+
message.createdAt,
|
|
25
|
+
"from:",
|
|
26
|
+
message
|
|
27
|
+
);
|
|
28
|
+
// Send delivery confirmation
|
|
29
|
+
// Update UI immediately
|
|
30
|
+
setMessages((prev) => [...prev, message]);
|
|
31
|
+
sendMessage({
|
|
32
|
+
event: "confirmDelivery",
|
|
33
|
+
data: {
|
|
34
|
+
messageIds: [message._id],
|
|
35
|
+
chatId: message.conversationId, // make sure this is sent from backend
|
|
36
|
+
},
|
|
37
|
+
});
|
|
22
38
|
|
|
23
|
-
|
|
24
|
-
updateMessageStatus(
|
|
39
|
+
// Optional: update UI
|
|
40
|
+
updateMessageStatus(message._id, "delivered");
|
|
25
41
|
}
|
|
26
42
|
} catch (error) {
|
|
27
|
-
console.error("
|
|
43
|
+
console.error("WebSocket message parse error:", error);
|
|
28
44
|
}
|
|
29
45
|
};
|
|
30
46
|
|
|
31
47
|
socket.addEventListener("message", handleMessage);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
socket.removeEventListener("message", handleMessage);
|
|
35
|
-
};
|
|
36
|
-
}, [socket, messages, setMessages, updateMessageStatus]);
|
|
48
|
+
return () => socket.removeEventListener("message", handleMessage);
|
|
49
|
+
}, [socket, setMessages, sendMessage, updateMessageStatus]);
|
|
37
50
|
|
|
38
51
|
return (
|
|
39
52
|
<div className="container mx-auto mb-5">
|
|
@@ -47,4 +60,4 @@ export const Chat = () => {
|
|
|
47
60
|
</div>
|
|
48
61
|
</div>
|
|
49
62
|
);
|
|
50
|
-
};
|
|
63
|
+
};
|
|
@@ -11,27 +11,27 @@ import { useDeleteMessageMutation } from "../../hooks/mutations/useDeleteMessage
|
|
|
11
11
|
import { useEffect, useRef, useState } from "react";
|
|
12
12
|
|
|
13
13
|
interface MessageProps {
|
|
14
|
-
|
|
15
|
-
_id?: string
|
|
16
|
-
senderId: string
|
|
17
|
-
message: string
|
|
18
|
-
status: MessageStatus
|
|
19
|
-
createdAt: any
|
|
20
|
-
updatedAt: any
|
|
14
|
+
message: {
|
|
15
|
+
_id?: string;
|
|
16
|
+
senderId: string;
|
|
17
|
+
message: string;
|
|
18
|
+
status: MessageStatus;
|
|
19
|
+
createdAt: any;
|
|
20
|
+
updatedAt: any;
|
|
21
21
|
media?: {
|
|
22
|
-
type: FileType
|
|
23
|
-
url: string
|
|
24
|
-
name: string
|
|
25
|
-
size: number
|
|
26
|
-
uploadProgress?: number
|
|
27
|
-
uploadError: string | null
|
|
28
|
-
}[]
|
|
29
|
-
isUploading?: boolean
|
|
30
|
-
isEdited?: boolean
|
|
31
|
-
isDeleted?: boolean
|
|
32
|
-
onEdit?: (messageId: string, newMessage: string) => void
|
|
33
|
-
onDelete?: (messageId: string) => void
|
|
34
|
-
type?:
|
|
22
|
+
type: FileType;
|
|
23
|
+
url: string;
|
|
24
|
+
name: string;
|
|
25
|
+
size: number;
|
|
26
|
+
uploadProgress?: number;
|
|
27
|
+
uploadError: string | null;
|
|
28
|
+
}[];
|
|
29
|
+
isUploading?: boolean;
|
|
30
|
+
isEdited?: boolean;
|
|
31
|
+
isDeleted?: boolean;
|
|
32
|
+
onEdit?: (messageId: string, newMessage: string) => void;
|
|
33
|
+
onDelete?: (messageId: string) => void;
|
|
34
|
+
type?: "user" | "system" | "system-completion";
|
|
35
35
|
meta?: {
|
|
36
36
|
bookingDetails?: {
|
|
37
37
|
serviceId: string;
|
|
@@ -41,8 +41,8 @@ interface MessageProps {
|
|
|
41
41
|
// Add other booking details as needed
|
|
42
42
|
};
|
|
43
43
|
reviewLink?: string;
|
|
44
|
-
|
|
45
|
-
}
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const Message = ({ message }: MessageProps) => {
|
|
@@ -91,8 +91,8 @@ const Message = ({ message }: MessageProps) => {
|
|
|
91
91
|
const { mutate: editMessage } = useEditMessageMutation();
|
|
92
92
|
const [editedMessage, setEditedMessage] = useState("");
|
|
93
93
|
const [isEditingMode, setIsEditingMode] = useState(false);
|
|
94
|
-
const { mutate: deleteMessage, isPending: isDeleting } =
|
|
95
|
-
|
|
94
|
+
const { mutate: deleteMessage, isPending: isDeleting } =
|
|
95
|
+
useDeleteMessageMutation();
|
|
96
96
|
|
|
97
97
|
useEffect(() => {
|
|
98
98
|
setLocalStatus(message.status);
|
|
@@ -565,7 +565,9 @@ const { mutate: deleteMessage, isPending: isDeleting } = useDeleteMessageMutatio
|
|
|
565
565
|
onClick={handleDeleteClick}
|
|
566
566
|
>
|
|
567
567
|
<Trash2 size={16} />
|
|
568
|
-
<span>
|
|
568
|
+
<span>
|
|
569
|
+
{isDeleting ? "Deleting..." : "Delete"}
|
|
570
|
+
</span>
|
|
569
571
|
</button>
|
|
570
572
|
</div>
|
|
571
573
|
)}
|
|
@@ -576,25 +578,23 @@ const { mutate: deleteMessage, isPending: isDeleting } = useDeleteMessageMutatio
|
|
|
576
578
|
)
|
|
577
579
|
)}
|
|
578
580
|
<div className={`${timestamp}`}>
|
|
579
|
-
{message.status ===
|
|
580
|
-
// Show deleted timestamp if message is deleted
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
// Show updated timestamp if message was edited
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
})
|
|
597
|
-
)}
|
|
581
|
+
{message.status === "deleted"
|
|
582
|
+
? // Show deleted timestamp if message is deleted
|
|
583
|
+
new Date(message.updatedAt).toLocaleTimeString([], {
|
|
584
|
+
hour: "2-digit",
|
|
585
|
+
minute: "2-digit",
|
|
586
|
+
})
|
|
587
|
+
: message.status === "edited"
|
|
588
|
+
? // Show updated timestamp if message was edited
|
|
589
|
+
new Date(message.updatedAt).toLocaleTimeString([], {
|
|
590
|
+
hour: "2-digit",
|
|
591
|
+
minute: "2-digit",
|
|
592
|
+
})
|
|
593
|
+
: // Default to created timestamp
|
|
594
|
+
new Date(message.createdAt).toLocaleTimeString([], {
|
|
595
|
+
hour: "2-digit",
|
|
596
|
+
minute: "2-digit",
|
|
597
|
+
})}
|
|
598
598
|
<span className="status-icon">{getStatusIcon()}</span>
|
|
599
599
|
</div>
|
|
600
600
|
</div>
|
|
@@ -11,58 +11,97 @@ const MessageContainer = () => {
|
|
|
11
11
|
setSelectedConversation,
|
|
12
12
|
onlineUsers,
|
|
13
13
|
setOnlineUsers,
|
|
14
|
+
setMessages,
|
|
14
15
|
} = useChatUIStore();
|
|
15
|
-
const { socket, sendMessage } = useChatContext();
|
|
16
|
-
const {role}= getChatConfig()
|
|
16
|
+
const { socket, sendMessage, isUserOnline } = useChatContext();
|
|
17
|
+
const { role } = getChatConfig();
|
|
17
18
|
const [joinedChats, setJoinedChats] = useState<Set<string>>(new Set());
|
|
18
19
|
|
|
20
|
+
// useEffect(() => {
|
|
21
|
+
// if (!socket) return;
|
|
22
|
+
|
|
23
|
+
// const handleMessage = (event) => {
|
|
24
|
+
// try {
|
|
25
|
+
// const parsed = JSON.parse(event.data);
|
|
26
|
+
|
|
27
|
+
// if (parsed.event === 'newMessage') {
|
|
28
|
+
// const message = parsed.data;
|
|
29
|
+
// console.log('Received message:', message);
|
|
30
|
+
|
|
31
|
+
// if (selectedConversation?._id !== message.chatId) return;
|
|
32
|
+
|
|
33
|
+
// const messageId = message._id || message.messageId;
|
|
34
|
+
// console.log('Message ID for unread:', messageId);
|
|
35
|
+
|
|
36
|
+
// if (!messageId) {
|
|
37
|
+
// console.warn('Message has no _id or messageId, skipping unread tracking');
|
|
38
|
+
// return;
|
|
39
|
+
// }
|
|
40
|
+
|
|
41
|
+
// const updatedUnread = [
|
|
42
|
+
// ...(selectedConversation?.unreadMessageIds || []),
|
|
43
|
+
// messageId,
|
|
44
|
+
// ];
|
|
45
|
+
|
|
46
|
+
// console.log('Updated unreadMessageIds:', updatedUnread);
|
|
47
|
+
|
|
48
|
+
// setSelectedConversation({
|
|
49
|
+
// ...selectedConversation,
|
|
50
|
+
// unreadMessageIds: updatedUnread,
|
|
51
|
+
// });
|
|
52
|
+
// }
|
|
53
|
+
|
|
54
|
+
// // Handle other events...
|
|
55
|
+
|
|
56
|
+
// } catch (error) {
|
|
57
|
+
// console.error("WebSocket message parse error:", error);
|
|
58
|
+
// }
|
|
59
|
+
// };
|
|
60
|
+
|
|
61
|
+
// socket.addEventListener("message", handleMessage);
|
|
62
|
+
// return () => socket.removeEventListener("message", handleMessage);
|
|
63
|
+
// }, [socket, setMessages, selectedConversation, setSelectedConversation]);
|
|
64
|
+
|
|
19
65
|
// Join chat room when conversation is selected
|
|
66
|
+
|
|
20
67
|
useEffect(() => {
|
|
21
68
|
if (selectedConversation?._id && socket?.readyState === WebSocket.OPEN) {
|
|
22
69
|
const chatId = selectedConversation._id;
|
|
23
|
-
|
|
70
|
+
const unreadMessages = selectedConversation.unreadMessageIds || [];
|
|
71
|
+
|
|
72
|
+
console.log("Unread messages:", unreadMessages);
|
|
73
|
+
|
|
74
|
+
if (unreadMessages.length > 0) {
|
|
24
75
|
sendMessage({
|
|
25
|
-
|
|
26
|
-
|
|
76
|
+
event: "messageRead",
|
|
77
|
+
data: {
|
|
78
|
+
messageIds: unreadMessages,
|
|
79
|
+
chatId: chatId,
|
|
80
|
+
},
|
|
27
81
|
});
|
|
28
|
-
setJoinedChats(new Set(joinedChats).add(chatId));
|
|
29
82
|
}
|
|
83
|
+
// }
|
|
30
84
|
}
|
|
31
|
-
}, [
|
|
85
|
+
}, [
|
|
86
|
+
selectedConversation?._id,
|
|
87
|
+
socket,
|
|
88
|
+
selectedConversation?.unreadMessageIds,
|
|
89
|
+
sendMessage,
|
|
90
|
+
joinedChats,
|
|
91
|
+
]);
|
|
32
92
|
|
|
33
93
|
// Listen for online users updates
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (!socket) return;
|
|
36
|
-
|
|
37
|
-
const handleMessage = (event: MessageEvent) => {
|
|
38
|
-
try {
|
|
39
|
-
const data = JSON.parse(event.data);
|
|
40
|
-
if (data.type === "getOnlineUsers") {
|
|
41
|
-
setOnlineUsers(data.users);
|
|
42
|
-
}
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.error("Error parsing message:", error);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
94
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return () => {
|
|
51
|
-
socket.removeEventListener("message", handleMessage);
|
|
52
|
-
};
|
|
53
|
-
}, [socket, setOnlineUsers]);
|
|
54
|
-
|
|
55
|
-
role === 'admin' && Array.isArray(selectedConversation?.participantDetails)
|
|
95
|
+
role === "admin" && Array.isArray(selectedConversation?.participantDetails)
|
|
56
96
|
? selectedConversation.participantDetails[1]?.profilePic
|
|
57
97
|
: !Array.isArray(selectedConversation?.participantDetails)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const isUserOnline =
|
|
62
|
-
!Array.isArray(selectedConversation?.participantDetails) &&
|
|
63
|
-
!!selectedConversation?.participantDetails?._id &&
|
|
64
|
-
onlineUsers?.includes(selectedConversation.participantDetails._id);
|
|
98
|
+
? selectedConversation?.participantDetails?.profilePic
|
|
99
|
+
: undefined;
|
|
65
100
|
|
|
101
|
+
const isOnline =
|
|
102
|
+
!Array.isArray(selectedConversation?.participantDetails) &&
|
|
103
|
+
!!selectedConversation?.participantDetails?._id &&
|
|
104
|
+
isUserOnline(selectedConversation.participantDetails._id);
|
|
66
105
|
|
|
67
106
|
// Cleanup on unmount
|
|
68
107
|
useEffect(() => {
|
|
@@ -84,34 +123,32 @@ const MessageContainer = () => {
|
|
|
84
123
|
className="chatMessageContainerInnerImg"
|
|
85
124
|
alt="Profile"
|
|
86
125
|
src={
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
126
|
+
role === "admin" &&
|
|
127
|
+
Array.isArray(selectedConversation?.participantDetails)
|
|
128
|
+
? selectedConversation.participantDetails[1]?.profilePic
|
|
129
|
+
: !Array.isArray(selectedConversation?.participantDetails)
|
|
130
|
+
? selectedConversation?.participantDetails?.profilePic
|
|
131
|
+
: undefined
|
|
92
132
|
}
|
|
93
133
|
/>
|
|
94
134
|
<div className="chatMessageContainerOutter">
|
|
95
135
|
<div className="chatMessageContainerOutterDiv">
|
|
96
136
|
<p className="chatMessageContainerOutterDiv_name">
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
</p>
|
|
104
|
-
<p className="text-sm">
|
|
105
|
-
{isUserOnline ? "Online" : "Offline"}
|
|
137
|
+
{role === "admin" &&
|
|
138
|
+
Array.isArray(selectedConversation?.participantDetails)
|
|
139
|
+
? selectedConversation.participantDetails[1]?.firstname
|
|
140
|
+
: !Array.isArray(selectedConversation?.participantDetails)
|
|
141
|
+
? selectedConversation?.participantDetails?.firstname
|
|
142
|
+
: undefined}
|
|
106
143
|
</p>
|
|
144
|
+
<p className="text-sm">{isOnline ? "Online" : "Offline"}</p>
|
|
107
145
|
</div>
|
|
108
146
|
</div>
|
|
109
147
|
</div>
|
|
110
148
|
</div>
|
|
111
149
|
|
|
112
150
|
<Messages />
|
|
113
|
-
{role !==
|
|
114
|
-
|
|
151
|
+
{role !== "admin" && <MessageInput />}
|
|
115
152
|
</>
|
|
116
153
|
)}
|
|
117
154
|
</div>
|
|
@@ -213,4 +250,4 @@ const EmptyInbox: React.FC<EmptyInboxProps> = ({
|
|
|
213
250
|
<p className="text-gray-500 max-w-sm">{description}</p>
|
|
214
251
|
</div>
|
|
215
252
|
);
|
|
216
|
-
};
|
|
253
|
+
};
|
|
@@ -227,7 +227,7 @@ const MessageInput = () => {
|
|
|
227
227
|
text: message1,
|
|
228
228
|
message: message1,
|
|
229
229
|
senderId: userId,
|
|
230
|
-
status: "
|
|
230
|
+
status: "pending" as MessageStatus,
|
|
231
231
|
createdAt: new Date().toISOString(),
|
|
232
232
|
media: attachmentsRef.current.map((att) => ({
|
|
233
233
|
type: att.type,
|
|
@@ -321,15 +321,17 @@ const MessageInput = () => {
|
|
|
321
321
|
|
|
322
322
|
// Send message via WebSocket
|
|
323
323
|
sendMessage({
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
324
|
+
event: "sendMessage",
|
|
325
|
+
data: {
|
|
326
|
+
chatId: selectedConversation?._id,
|
|
327
|
+
message: message1,
|
|
328
|
+
messageId: data[1]._id,
|
|
329
|
+
attachments: successfulUploads,
|
|
330
|
+
senderId: userId,
|
|
331
|
+
receiverId:
|
|
332
|
+
!Array.isArray(selectedConversation?.participantDetails) &&
|
|
333
|
+
selectedConversation?.participantDetails._id,
|
|
334
|
+
},
|
|
333
335
|
});
|
|
334
336
|
},
|
|
335
337
|
onError: (error) => {
|
|
@@ -42,12 +42,12 @@ const Messages = () => {
|
|
|
42
42
|
|
|
43
43
|
const handleMessage = (event: MessageEvent) => {
|
|
44
44
|
try {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
const newMessage =
|
|
45
|
+
const parsed = JSON.parse(event.data);
|
|
46
|
+
console.log("Parsed WebSocket message1:", parsed);
|
|
47
|
+
if (parsed.type === "newMessage" || parsed.event === "newMessage") {
|
|
48
|
+
const newMessage = parsed.message;
|
|
49
49
|
newMessage.shouldShake = true;
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
setMessages((prevMessages) => {
|
|
52
52
|
const isDuplicate = prevMessages.some(
|
|
53
53
|
(msg) =>
|
|
@@ -61,11 +61,28 @@ const Messages = () => {
|
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
const statusOrder = ["sent", "delivered", "read"];
|
|
65
|
+
if (parsed.event === "messageStatusUpdated") {
|
|
66
|
+
const { messageId, status } = parsed.data || {};
|
|
67
|
+
if (!messageId) {
|
|
68
|
+
console.error("Missing messageId in status update", parsed);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(`Updating status for ${messageId} to ${status}`);
|
|
65
73
|
setMessages((prev) =>
|
|
66
|
-
prev.map((msg) =>
|
|
67
|
-
msg._id
|
|
68
|
-
|
|
74
|
+
prev.map((msg) => {
|
|
75
|
+
if (msg._id !== messageId) return msg;
|
|
76
|
+
|
|
77
|
+
// Only update if new status is higher than current status
|
|
78
|
+
const currentIdx = statusOrder.indexOf(msg.status);
|
|
79
|
+
const newIdx = statusOrder.indexOf(status);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...msg,
|
|
83
|
+
status: newIdx > currentIdx ? status : msg.status,
|
|
84
|
+
};
|
|
85
|
+
})
|
|
69
86
|
);
|
|
70
87
|
}
|
|
71
88
|
} catch (error) {
|
|
@@ -80,11 +97,29 @@ const Messages = () => {
|
|
|
80
97
|
};
|
|
81
98
|
}, [socket, selectedConversation?._id, setMessages, userId]);
|
|
82
99
|
|
|
100
|
+
const sendDeliveryConfirmation = (messageId: string) => {
|
|
101
|
+
if (!socket) return;
|
|
102
|
+
|
|
103
|
+
const message = {
|
|
104
|
+
event: "confirmDelivery",
|
|
105
|
+
data: {
|
|
106
|
+
messageId,
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// timestamp: Date.now()
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
socket.send(JSON.stringify(message));
|
|
113
|
+
};
|
|
114
|
+
|
|
83
115
|
// Scroll to bottom when messages change
|
|
84
116
|
useEffect(() => {
|
|
85
117
|
if (messages.length > 0) {
|
|
86
118
|
setTimeout(() => {
|
|
87
|
-
lastMessageRef.current?.scrollIntoView({
|
|
119
|
+
lastMessageRef.current?.scrollIntoView({
|
|
120
|
+
behavior: "smooth",
|
|
121
|
+
block: "end",
|
|
122
|
+
});
|
|
88
123
|
}, 100);
|
|
89
124
|
}
|
|
90
125
|
}, [messages.length]);
|
|
@@ -113,19 +148,6 @@ const Messages = () => {
|
|
|
113
148
|
return () => observer.disconnect();
|
|
114
149
|
}, [messages, socket]);
|
|
115
150
|
|
|
116
|
-
|
|
117
|
-
const sendDeliveryConfirmation = (messageId: string) => {
|
|
118
|
-
if (!socket) return;
|
|
119
|
-
|
|
120
|
-
const message = {
|
|
121
|
-
type: "confirmDelivery",
|
|
122
|
-
messageId,
|
|
123
|
-
// timestamp: Date.now()
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
socket.send(JSON.stringify(message));
|
|
127
|
-
};
|
|
128
|
-
|
|
129
151
|
return (
|
|
130
152
|
<div
|
|
131
153
|
className="chatMessages"
|
|
@@ -157,4 +179,4 @@ const Messages = () => {
|
|
|
157
179
|
);
|
|
158
180
|
};
|
|
159
181
|
|
|
160
|
-
export default Messages;
|
|
182
|
+
export default Messages;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
2
|
import { useChatContext } from "../../providers/ChatProvider";
|
|
3
3
|
import useChatUIStore from "../../stores/Zustant";
|
|
4
4
|
import { ConversationProps } from "../../types/type";
|
|
@@ -9,53 +9,103 @@ const Conversation = ({ conversation }: ConversationProps) => {
|
|
|
9
9
|
setSelectedConversation,
|
|
10
10
|
setOnlineUsers,
|
|
11
11
|
onlineUsers,
|
|
12
|
+
setMessages,
|
|
12
13
|
selectedConversation,
|
|
14
|
+
updateMessageStatus,
|
|
13
15
|
} = useChatUIStore();
|
|
14
|
-
const { socket, sendMessage } = useChatContext();
|
|
16
|
+
const { socket, sendMessage, isUserOnline } = useChatContext();
|
|
15
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
|
+
// };
|
|
37
|
+
|
|
38
|
+
const [activeChatId, setActiveChatId] = useState(null);
|
|
39
|
+
|
|
16
40
|
const handleSelectConversation = async () => {
|
|
41
|
+
// Set as selected conversation
|
|
17
42
|
setSelectedConversation(conversation);
|
|
18
43
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.log("Marking messages as read:", unreadMessages);
|
|
44
|
+
// Mark as active chat
|
|
45
|
+
setActiveChatId(conversation._id);
|
|
22
46
|
|
|
47
|
+
// Join chat via WebSocket
|
|
48
|
+
if (socket?.readyState === WebSocket.OPEN) {
|
|
23
49
|
sendMessage({
|
|
24
|
-
event: "
|
|
50
|
+
event: "joinChat",
|
|
25
51
|
data: {
|
|
26
|
-
messageIds: unreadMessages,
|
|
27
52
|
chatId: conversation._id,
|
|
53
|
+
// Send any existing unread messages to mark as read
|
|
54
|
+
messageIds: conversation.unreadMessageIds || [],
|
|
28
55
|
},
|
|
29
56
|
});
|
|
30
57
|
}
|
|
31
58
|
};
|
|
32
59
|
|
|
33
|
-
//
|
|
60
|
+
// // Enhanced message handler
|
|
34
61
|
useEffect(() => {
|
|
35
62
|
if (!socket) return;
|
|
36
63
|
|
|
37
64
|
const handleMessage = (event: MessageEvent) => {
|
|
38
65
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
}
|
|
42
97
|
}
|
|
43
98
|
} catch (error) {
|
|
44
|
-
console.error("Error
|
|
99
|
+
console.error("Error handling message:", error);
|
|
45
100
|
}
|
|
46
101
|
};
|
|
47
102
|
|
|
48
103
|
socket.addEventListener("message", handleMessage);
|
|
104
|
+
return () => socket.removeEventListener("message", handleMessage);
|
|
105
|
+
}, [socket, activeChatId, setMessages, updateMessageStatus]);
|
|
49
106
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
};
|
|
53
|
-
}, [socket, setOnlineUsers]);
|
|
54
|
-
|
|
55
|
-
const isUserOnline =
|
|
56
|
-
conversation?.participantDetails?._id &&
|
|
57
|
-
onlineUsers?.includes(conversation.participantDetails._id);
|
|
58
|
-
|
|
107
|
+
const isOnline = isUserOnline(conversation?.participantDetails?._id || "");
|
|
108
|
+
console.log("Online status:", isOnline);
|
|
59
109
|
const isSelected = selectedConversation?._id === conversation._id;
|
|
60
110
|
|
|
61
111
|
return (
|
|
@@ -69,16 +119,16 @@ const Conversation = ({ conversation }: ConversationProps) => {
|
|
|
69
119
|
className="conversation-img"
|
|
70
120
|
src={
|
|
71
121
|
role === "admin" &&
|
|
72
|
-
Array.isArray(
|
|
73
|
-
?
|
|
74
|
-
: !Array.isArray(
|
|
75
|
-
?
|
|
122
|
+
Array.isArray(conversation?.participantDetails)
|
|
123
|
+
? conversation.participantDetails[1]?.profilePic
|
|
124
|
+
: !Array.isArray(conversation?.participantDetails)
|
|
125
|
+
? conversation?.participantDetails?.profilePic
|
|
76
126
|
: undefined
|
|
77
127
|
}
|
|
78
128
|
alt="User Avatar"
|
|
79
129
|
/>
|
|
80
130
|
<span
|
|
81
|
-
className={`chatSidebarStatusDot ${
|
|
131
|
+
className={`chatSidebarStatusDot ${isOnline && "online"}`}
|
|
82
132
|
></span>
|
|
83
133
|
</div>
|
|
84
134
|
|
|
@@ -86,32 +136,36 @@ const Conversation = ({ conversation }: ConversationProps) => {
|
|
|
86
136
|
<div className="conversation-header">
|
|
87
137
|
<p className="conversation-name">
|
|
88
138
|
{role === "admin" &&
|
|
89
|
-
Array.isArray(
|
|
90
|
-
?
|
|
91
|
-
: !Array.isArray(
|
|
92
|
-
?
|
|
139
|
+
Array.isArray(conversation?.participantDetails)
|
|
140
|
+
? conversation.participantDetails[1]?.firstname
|
|
141
|
+
: !Array.isArray(conversation?.participantDetails)
|
|
142
|
+
? conversation?.participantDetails?.firstname
|
|
93
143
|
: undefined}
|
|
94
144
|
</p>
|
|
95
145
|
<span className="conversation-time">
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
+
})}
|
|
115
169
|
</span>
|
|
116
170
|
</div>
|
|
117
171
|
<p className="conversation-message">
|
|
@@ -12,7 +12,12 @@ const Conversations = () => {
|
|
|
12
12
|
// const { loading, conversations } = useGetConversations();
|
|
13
13
|
return (
|
|
14
14
|
<div className="chatSidebarConversations">
|
|
15
|
-
<h2
|
|
15
|
+
<h2
|
|
16
|
+
className="text-lg font-semibold text-gray-700"
|
|
17
|
+
style={{ paddingLeft: "1rem" }}
|
|
18
|
+
>
|
|
19
|
+
All Messages
|
|
20
|
+
</h2>
|
|
16
21
|
{(!conversations || conversations.length === 0) && (
|
|
17
22
|
<div className="flex flex-col items-center justify-center p-8 text-center">
|
|
18
23
|
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-gray-100 mb-4">
|
|
@@ -18,9 +18,11 @@ interface ChatProviderProps {
|
|
|
18
18
|
interface ChatContextType {
|
|
19
19
|
socket: WebSocket | null;
|
|
20
20
|
userId: string;
|
|
21
|
-
onlineUsers: any[];
|
|
21
|
+
// onlineUsers: any[];
|
|
22
22
|
sendMessage: (data: any) => void;
|
|
23
23
|
isConnected: boolean;
|
|
24
|
+
onlineUsers: Set<string>; // Change to Set for better performance
|
|
25
|
+
isUserOnline: (userId: string) => boolean; // Add helper function
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const ChatContext = createContext<ChatContextType | null>(null);
|
|
@@ -31,7 +33,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
31
33
|
}) => {
|
|
32
34
|
const socketRef = useRef<WebSocket | null>(null);
|
|
33
35
|
const [socket, setSocket] = useState<WebSocket | null>(null);
|
|
34
|
-
const [onlineUsers, setOnlineUsers] = useState<any[]>([]);
|
|
36
|
+
// const [onlineUsers, setOnlineUsers] = useState<any[]>([]);
|
|
37
|
+
const [onlineUsers, setOnlineUsers] = useState<Set<string>>(new Set());
|
|
35
38
|
const [isConnected, setIsConnected] = useState(false);
|
|
36
39
|
const reconnectAttempts = useRef(0);
|
|
37
40
|
const maxReconnectAttempts = 5;
|
|
@@ -40,35 +43,40 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
40
43
|
|
|
41
44
|
const connectWebSocket = useCallback(() => {
|
|
42
45
|
console.log("🔌 Creating new WebSocket connection...");
|
|
43
|
-
|
|
46
|
+
|
|
44
47
|
// Convert HTTP URL to WebSocket URL
|
|
45
|
-
const wsUrl = apiUrl.replace(/^http:/,
|
|
46
|
-
const socketInstance = new WebSocket(
|
|
48
|
+
const wsUrl = apiUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:");
|
|
49
|
+
const socketInstance = new WebSocket(
|
|
50
|
+
`${wsUrl}?userId=${encodeURIComponent(userId)}`
|
|
51
|
+
);
|
|
47
52
|
|
|
48
53
|
socketInstance.onopen = () => {
|
|
49
54
|
console.log("✅ WebSocket connected");
|
|
50
55
|
setIsConnected(true);
|
|
51
56
|
reconnectAttempts.current = 0;
|
|
52
|
-
|
|
57
|
+
|
|
53
58
|
// Send initial handshake if needed
|
|
54
|
-
socketInstance.send(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
socketInstance.send(
|
|
60
|
+
JSON.stringify({
|
|
61
|
+
event: "handshake",
|
|
62
|
+
userId: userId,
|
|
63
|
+
})
|
|
64
|
+
);
|
|
58
65
|
};
|
|
59
66
|
|
|
60
67
|
socketInstance.onmessage = (event) => {
|
|
61
68
|
try {
|
|
62
69
|
const data = JSON.parse(event.data);
|
|
63
|
-
|
|
64
|
-
if (data.
|
|
65
|
-
|
|
70
|
+
|
|
71
|
+
if (data.event === "getOnlineUsers") {
|
|
72
|
+
console.log("Online users update:", data.data);
|
|
73
|
+
setOnlineUsers(new Set(data.data));
|
|
66
74
|
}
|
|
67
|
-
|
|
75
|
+
|
|
68
76
|
// Handle other message types here
|
|
69
|
-
console.log(
|
|
77
|
+
console.log("Received message:", data);
|
|
70
78
|
} catch (error) {
|
|
71
|
-
console.error(
|
|
79
|
+
console.error("Error parsing message:", error);
|
|
72
80
|
}
|
|
73
81
|
};
|
|
74
82
|
|
|
@@ -80,11 +88,13 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
80
88
|
console.log("🔌 WebSocket connection closed", event);
|
|
81
89
|
console.log("❌ WebSocket disconnected:", event.code, event.reason);
|
|
82
90
|
setIsConnected(false);
|
|
83
|
-
|
|
91
|
+
|
|
84
92
|
// Attempt reconnection
|
|
85
93
|
if (reconnectAttempts.current < maxReconnectAttempts) {
|
|
86
94
|
reconnectAttempts.current += 1;
|
|
87
|
-
console.log(
|
|
95
|
+
console.log(
|
|
96
|
+
`Attempting to reconnect (${reconnectAttempts.current}/${maxReconnectAttempts})...`
|
|
97
|
+
);
|
|
88
98
|
setTimeout(connectWebSocket, reconnectInterval);
|
|
89
99
|
}
|
|
90
100
|
};
|
|
@@ -113,14 +123,24 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
113
123
|
};
|
|
114
124
|
}, [connectWebSocket]);
|
|
115
125
|
|
|
126
|
+
const isUserOnline = useCallback(
|
|
127
|
+
(userId: string) => {
|
|
128
|
+
return onlineUsers.has(userId);
|
|
129
|
+
},
|
|
130
|
+
[onlineUsers]
|
|
131
|
+
);
|
|
132
|
+
|
|
116
133
|
return (
|
|
117
|
-
<ChatContext.Provider
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
134
|
+
<ChatContext.Provider
|
|
135
|
+
value={{
|
|
136
|
+
socket,
|
|
137
|
+
userId,
|
|
138
|
+
onlineUsers,
|
|
139
|
+
sendMessage,
|
|
140
|
+
isConnected,
|
|
141
|
+
isUserOnline,
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
124
144
|
{children}
|
|
125
145
|
</ChatContext.Provider>
|
|
126
146
|
);
|
|
@@ -132,4 +152,4 @@ export const useChatContext = () => {
|
|
|
132
152
|
throw new Error("useChatContext must be used within a ChatProvider");
|
|
133
153
|
}
|
|
134
154
|
return context;
|
|
135
|
-
};
|
|
155
|
+
};
|
package/src/stores/Zustant.ts
CHANGED
|
@@ -12,28 +12,30 @@ interface ChatUIState {
|
|
|
12
12
|
firstname: string;
|
|
13
13
|
idpic: string;
|
|
14
14
|
};
|
|
15
|
+
unreadMessageIds?: string[];
|
|
15
16
|
_id: string;
|
|
16
17
|
} | null;
|
|
17
18
|
setSelectedConversation: (
|
|
18
19
|
selectedConversation: ChatUIState["selectedConversation"]
|
|
19
20
|
) => void;
|
|
20
|
-
messages: {
|
|
21
|
-
_id: string;
|
|
22
|
-
text: string;
|
|
23
|
-
senderId: string;
|
|
24
|
-
status: string
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
messages: {
|
|
22
|
+
_id: string;
|
|
23
|
+
text: string;
|
|
24
|
+
senderId: string;
|
|
25
|
+
status: string;
|
|
26
|
+
isOptimistic: boolean;
|
|
27
|
+
message: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
media: {
|
|
30
|
+
type: FileType;
|
|
31
|
+
url: string;
|
|
32
|
+
name: string;
|
|
33
|
+
size: number;
|
|
34
|
+
uploadProgress: number;
|
|
35
|
+
uploadError: string
|
|
36
|
+
}[];
|
|
37
|
+
isUploading: boolean;
|
|
38
|
+
}[];
|
|
37
39
|
setMessages: (messages: ChatUIState["messages"] | ((prev: ChatUIState["messages"]) => ChatUIState["messages"])) => void;
|
|
38
40
|
updateMessageStatus: (messageId: string, status: string) => void;
|
|
39
41
|
toggleChat: () => void;
|
package/src/style/style.css
CHANGED
|
@@ -51,13 +51,15 @@
|
|
|
51
51
|
display: flex;
|
|
52
52
|
padding: 0.5rem 1rem;
|
|
53
53
|
width: 100%;
|
|
54
|
-
border: 1px solid #ccc;
|
|
54
|
+
border: 1px solid #ccc;
|
|
55
|
+
/* Consistent border color */
|
|
55
56
|
border-radius: 4px;
|
|
56
57
|
gap: 1rem;
|
|
57
58
|
justify-content: flex-start;
|
|
58
59
|
align-items: center;
|
|
59
60
|
height: 2.5rem;
|
|
60
|
-
background-color: rgb(248, 249, 249);
|
|
61
|
+
background-color: rgb(248, 249, 249);
|
|
62
|
+
/* Move bg color here */
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
.chatSidebarSearchbarImg {
|
|
@@ -77,9 +79,12 @@
|
|
|
77
79
|
font-weight: 400;
|
|
78
80
|
width: 100%;
|
|
79
81
|
max-width: 600px;
|
|
80
|
-
border: none !important;
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
border: none !important;
|
|
83
|
+
/* Force no border */
|
|
84
|
+
background: transparent;
|
|
85
|
+
/* Inherit from container */
|
|
86
|
+
box-shadow: none !important;
|
|
87
|
+
/* Remove any shadow */
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
.chatSidebarSearchbarContainer:focus-within {
|
|
@@ -379,7 +384,8 @@
|
|
|
379
384
|
width: 40px;
|
|
380
385
|
height: 40px;
|
|
381
386
|
padding: 0;
|
|
382
|
-
border-radius: 6px;
|
|
387
|
+
border-radius: 6px;
|
|
388
|
+
/* or 0px for sharp corners */
|
|
383
389
|
cursor: pointer;
|
|
384
390
|
display: inline-flex;
|
|
385
391
|
align-items: center;
|
|
@@ -435,23 +441,23 @@
|
|
|
435
441
|
position: relative;
|
|
436
442
|
}
|
|
437
443
|
|
|
438
|
-
.amk7{
|
|
439
|
-
position: absolute;
|
|
440
|
-
top: 0;
|
|
441
|
-
display: block;
|
|
442
|
-
width: 8px;
|
|
443
|
-
height: 13px;
|
|
444
|
-
right: -8px;
|
|
444
|
+
.amk7 {
|
|
445
|
+
position: absolute;
|
|
446
|
+
top: 0;
|
|
447
|
+
display: block;
|
|
448
|
+
width: 8px;
|
|
449
|
+
height: 13px;
|
|
450
|
+
right: -8px;
|
|
445
451
|
}
|
|
446
452
|
|
|
447
|
-
:root{
|
|
448
|
-
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
453
|
+
:root {
|
|
454
|
+
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
449
455
|
}
|
|
450
456
|
|
|
451
457
|
.chat-bubble {
|
|
452
458
|
|
|
453
459
|
background-color: #f3f4f6;
|
|
454
|
-
border-radius: 0.5rem
|
|
460
|
+
border-radius: 0.5rem;
|
|
455
461
|
/* padding: 0.5rem; */
|
|
456
462
|
font-size: 14px;
|
|
457
463
|
max-width: 20rem;
|
|
@@ -1191,17 +1197,17 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
|
|
|
1191
1197
|
/* margin-top: 0.5rem; */
|
|
1192
1198
|
}
|
|
1193
1199
|
|
|
1194
|
-
|
|
1200
|
+
.media-item {
|
|
1195
1201
|
background: #f3f4f6;
|
|
1196
1202
|
border-radius: 0.375rem;
|
|
1197
1203
|
/* reduced from 0.75rem */
|
|
1198
|
-
|
|
1204
|
+
padding: 0.5rem;
|
|
1199
1205
|
overflow: hidden;
|
|
1200
1206
|
display: flex;
|
|
1201
1207
|
flex-direction: column;
|
|
1202
1208
|
align-items: start;
|
|
1203
1209
|
width: 246px;
|
|
1204
|
-
}
|
|
1210
|
+
}
|
|
1205
1211
|
|
|
1206
1212
|
/* .media-grid {
|
|
1207
1213
|
display: grid;
|
|
@@ -1243,15 +1249,15 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
|
|
|
1243
1249
|
position: relative;
|
|
1244
1250
|
width: 246px;
|
|
1245
1251
|
height: 100%; */
|
|
1246
|
-
|
|
1247
|
-
|
|
1252
|
+
/* aspect-ratio: 1; */
|
|
1253
|
+
/* overflow: hidden;
|
|
1248
1254
|
} */
|
|
1249
1255
|
|
|
1250
1256
|
/* .media-item img {
|
|
1251
1257
|
width: 100%;
|
|
1252
1258
|
height: 100%;
|
|
1253
1259
|
object-fit: cover; */
|
|
1254
|
-
|
|
1260
|
+
/* display: block; */
|
|
1255
1261
|
/* } */
|
|
1256
1262
|
|
|
1257
1263
|
/* Adjust aspect ratio for single media */
|
|
@@ -1277,7 +1283,8 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
|
|
|
1277
1283
|
.four-media-item
|
|
1278
1284
|
{
|
|
1279
1285
|
width: 100%;
|
|
1280
|
-
} */
|
|
1286
|
+
} */
|
|
1287
|
+
*/
|
|
1281
1288
|
|
|
1282
1289
|
/* Optional: Add overlay for additional items count */
|
|
1283
1290
|
.media-item.count-overlay {
|
|
@@ -1351,7 +1358,7 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
|
|
|
1351
1358
|
|
|
1352
1359
|
.document-preview {
|
|
1353
1360
|
position: relative;
|
|
1354
|
-
width: 226px;
|
|
1361
|
+
width: 226px;
|
|
1355
1362
|
height: 60px;
|
|
1356
1363
|
/* Increased height to accommodate filename */
|
|
1357
1364
|
border-radius: 12px;
|
|
@@ -1560,6 +1567,7 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
|
|
|
1560
1567
|
background-color: #f44336;
|
|
1561
1568
|
color: white;
|
|
1562
1569
|
}
|
|
1570
|
+
|
|
1563
1571
|
.system-message.booking-details {
|
|
1564
1572
|
display: flex;
|
|
1565
1573
|
flex-direction: column;
|
package/src/types/type.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface ParticipantDetails {
|
|
|
21
21
|
export interface Conversation {
|
|
22
22
|
_id: string;
|
|
23
23
|
createdAt: string;
|
|
24
|
-
lastMessage:{
|
|
24
|
+
lastMessage: {
|
|
25
25
|
_id: string;
|
|
26
26
|
senderId: string;
|
|
27
27
|
message: string;
|
|
@@ -47,12 +47,12 @@ export interface ApiResponse {
|
|
|
47
47
|
|
|
48
48
|
export interface ConversationProps {
|
|
49
49
|
conversation: {
|
|
50
|
-
lastMessage:{
|
|
50
|
+
lastMessage: {
|
|
51
51
|
_id: string;
|
|
52
52
|
senderId: string;
|
|
53
53
|
message: string;
|
|
54
|
-
media:string[];
|
|
55
|
-
type?:'user' | 'system' | 'system-completion';
|
|
54
|
+
media: string[];
|
|
55
|
+
type?: 'user' | 'system' | 'system-completion';
|
|
56
56
|
status: MessageStatus;
|
|
57
57
|
chatId: string;
|
|
58
58
|
createdAt: string;
|