@pubuduth-aplicy/chat-ui 2.1.71 → 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.
- package/package.json +2 -1
- package/src/components/Chat.tsx +30 -17
- package/src/components/common/CollapsibleSection.tsx +40 -0
- package/src/components/common/VirtualizedChatList.tsx +57 -0
- package/src/components/messages/Message.tsx +46 -45
- package/src/components/messages/MessageContainer.tsx +116 -59
- package/src/components/messages/MessageInput.tsx +59 -18
- package/src/components/messages/Messages.tsx +97 -45
- package/src/components/sidebar/Conversation.tsx +113 -100
- package/src/components/sidebar/Conversations.tsx +270 -35
- package/src/components/sidebar/SearchInput.tsx +16 -54
- package/src/hooks/useMessageStatus.ts +97 -0
- package/src/providers/ChatProvider.tsx +46 -26
- package/src/service/messageService.ts +86 -61
- package/src/stores/Zustant.ts +28 -21
- package/src/style/style.css +200 -24
- package/src/types/type.ts +25 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pubuduth-aplicy/chat-ui",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.74",
|
|
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": "",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@tanstack/react-query": "^5.67.2",
|
|
16
|
+
"@tanstack/react-virtual": "^3.13.12",
|
|
16
17
|
"axios": "^1.8.2",
|
|
17
18
|
"lucide-react": "^0.514.0",
|
|
18
19
|
"react": "^19.0.0",
|
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
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { ChevronDown } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
interface CollapsibleSectionProps {
|
|
5
|
+
title: string;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
defaultOpen?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const CollapsibleSection = ({
|
|
11
|
+
title,
|
|
12
|
+
children,
|
|
13
|
+
defaultOpen = true,
|
|
14
|
+
}: CollapsibleSectionProps) => {
|
|
15
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
16
|
+
|
|
17
|
+
const toggleOpen = () => {
|
|
18
|
+
setIsOpen(!isOpen);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="border-b border-gray-200">
|
|
23
|
+
<div
|
|
24
|
+
className="flex justify-between items-center p-2 cursor-pointer hover:bg-gray-50"
|
|
25
|
+
onClick={toggleOpen}
|
|
26
|
+
>
|
|
27
|
+
<p className="text-xs uppercase">{title}</p>
|
|
28
|
+
<ChevronDown
|
|
29
|
+
size={20}
|
|
30
|
+
className={`transform transition-transform duration-200 ${
|
|
31
|
+
isOpen ? "rotate-180" : ""
|
|
32
|
+
}`}
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
{isOpen && <div className="p-2">{children}</div>}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default CollapsibleSection;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
3
|
+
import Conversation from "../sidebar/Conversation";
|
|
4
|
+
import { Conversation as ConversationType } from "../../types/type";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
conversations: ConversationType[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ITEM_HEIGHT = 60;
|
|
11
|
+
|
|
12
|
+
const VirtualizedChatList = ({ conversations }: Props) => {
|
|
13
|
+
const parentRef = useRef<HTMLDivElement>(null);
|
|
14
|
+
|
|
15
|
+
const virtualizer = useVirtualizer({
|
|
16
|
+
count: conversations.length,
|
|
17
|
+
getScrollElement: () => parentRef.current,
|
|
18
|
+
estimateSize: () => ITEM_HEIGHT,
|
|
19
|
+
overscan: 5,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
ref={parentRef}
|
|
25
|
+
style={{
|
|
26
|
+
height: `${virtualizer.getTotalSize()}px`,
|
|
27
|
+
width: "100%",
|
|
28
|
+
position: "relative",
|
|
29
|
+
overflow: "auto",
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{virtualizer.getVirtualItems().map((item) => {
|
|
33
|
+
const conversation = conversations[item.index];
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
key={item.key}
|
|
37
|
+
style={{
|
|
38
|
+
position: "absolute",
|
|
39
|
+
top: 0,
|
|
40
|
+
left: 0,
|
|
41
|
+
width: "100%",
|
|
42
|
+
height: `${item.size}px`,
|
|
43
|
+
transform: `translateY(${item.start}px)`,
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<Conversation
|
|
47
|
+
conversation={conversation}
|
|
48
|
+
lastIdx={item.index === conversations.length - 1}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
})}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default VirtualizedChatList;
|
|
@@ -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);
|
|
@@ -101,6 +101,7 @@ const { mutate: deleteMessage, isPending: isDeleting } = useDeleteMessageMutatio
|
|
|
101
101
|
// const handleDownload = (url: string, name: string) => {
|
|
102
102
|
// saveAs(url, name);
|
|
103
103
|
// };
|
|
104
|
+
console.log("check message status", message.status);
|
|
104
105
|
|
|
105
106
|
const [downloadingIndex, setDownloadingIndex] = useState<number | null>(null);
|
|
106
107
|
const [downloadProgress, setDownloadProgress] = useState<number>(0);
|
|
@@ -499,7 +500,7 @@ const { mutate: deleteMessage, isPending: isDeleting } = useDeleteMessageMutatio
|
|
|
499
500
|
fromMe && !showDeleteOption && setShowOptions(false)
|
|
500
501
|
}
|
|
501
502
|
>
|
|
502
|
-
{message.
|
|
503
|
+
{message.status === "deleted" ? (
|
|
503
504
|
<div className="chat-bubble compact-bubble deleted-message">
|
|
504
505
|
<div className="message-text">This message was deleted</div>
|
|
505
506
|
</div>
|
|
@@ -565,7 +566,9 @@ const { mutate: deleteMessage, isPending: isDeleting } = useDeleteMessageMutatio
|
|
|
565
566
|
onClick={handleDeleteClick}
|
|
566
567
|
>
|
|
567
568
|
<Trash2 size={16} />
|
|
568
|
-
<span>
|
|
569
|
+
<span>
|
|
570
|
+
{isDeleting ? "Deleting..." : "Delete"}
|
|
571
|
+
</span>
|
|
569
572
|
</button>
|
|
570
573
|
</div>
|
|
571
574
|
)}
|
|
@@ -576,25 +579,23 @@ const { mutate: deleteMessage, isPending: isDeleting } = useDeleteMessageMutatio
|
|
|
576
579
|
)
|
|
577
580
|
)}
|
|
578
581
|
<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
|
-
)}
|
|
582
|
+
{message.status === "deleted"
|
|
583
|
+
? // Show deleted timestamp if message is deleted
|
|
584
|
+
new Date(message.updatedAt).toLocaleTimeString([], {
|
|
585
|
+
hour: "2-digit",
|
|
586
|
+
minute: "2-digit",
|
|
587
|
+
})
|
|
588
|
+
: message.status === "edited"
|
|
589
|
+
? // Show updated timestamp if message was edited
|
|
590
|
+
new Date(message.updatedAt).toLocaleTimeString([], {
|
|
591
|
+
hour: "2-digit",
|
|
592
|
+
minute: "2-digit",
|
|
593
|
+
})
|
|
594
|
+
: // Default to created timestamp
|
|
595
|
+
new Date(message.createdAt).toLocaleTimeString([], {
|
|
596
|
+
hour: "2-digit",
|
|
597
|
+
minute: "2-digit",
|
|
598
|
+
})}
|
|
598
599
|
<span className="status-icon">{getStatusIcon()}</span>
|
|
599
600
|
</div>
|
|
600
601
|
</div>
|
|
@@ -1,47 +1,91 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { MessageSquare } from "lucide-react";
|
|
3
|
+
import { useEffect } from "react";
|
|
2
4
|
import MessageInput from "./MessageInput";
|
|
3
5
|
import Messages from "./Messages";
|
|
4
6
|
import useChatUIStore from "../../stores/Zustant";
|
|
5
7
|
import { useChatContext } from "../../providers/ChatProvider";
|
|
6
|
-
import { getChatConfig } from "
|
|
8
|
+
import { getChatConfig } from "../../Chat.config";
|
|
7
9
|
|
|
8
10
|
const MessageContainer = () => {
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
11
|
+
const { selectedConversation, setSelectedConversation, setOnlineUsers } =
|
|
12
|
+
useChatUIStore();
|
|
13
|
+
const { socket, isUserOnline } = useChatContext();
|
|
14
|
+
const { role } = getChatConfig();
|
|
15
|
+
// useEffect(() => {
|
|
16
|
+
// if (!socket) return;
|
|
17
|
+
|
|
18
|
+
// const handleMessage = (event) => {
|
|
19
|
+
// try {
|
|
20
|
+
// const parsed = JSON.parse(event.data);
|
|
21
|
+
|
|
22
|
+
// if (parsed.event === 'newMessage') {
|
|
23
|
+
// const message = parsed.data;
|
|
24
|
+
// console.log('Received message:', message);
|
|
25
|
+
|
|
26
|
+
// if (selectedConversation?._id !== message.chatId) return;
|
|
27
|
+
|
|
28
|
+
// const messageId = message._id || message.messageId;
|
|
29
|
+
// console.log('Message ID for unread:', messageId);
|
|
30
|
+
|
|
31
|
+
// if (!messageId) {
|
|
32
|
+
// console.warn('Message has no _id or messageId, skipping unread tracking');
|
|
33
|
+
// return;
|
|
34
|
+
// }
|
|
35
|
+
|
|
36
|
+
// const updatedUnread = [
|
|
37
|
+
// ...(selectedConversation?.unreadMessageIds || []),
|
|
38
|
+
// messageId,
|
|
39
|
+
// ];
|
|
40
|
+
|
|
41
|
+
// console.log('Updated unreadMessageIds:', updatedUnread);
|
|
42
|
+
|
|
43
|
+
// setSelectedConversation({
|
|
44
|
+
// ...selectedConversation,
|
|
45
|
+
// unreadMessageIds: updatedUnread,
|
|
46
|
+
// });
|
|
47
|
+
// }
|
|
48
|
+
|
|
49
|
+
// // Handle other events...
|
|
50
|
+
|
|
51
|
+
// } catch (error) {
|
|
52
|
+
// console.error("WebSocket message parse error:", error);
|
|
53
|
+
// }
|
|
54
|
+
// };
|
|
55
|
+
|
|
56
|
+
// socket.addEventListener("message", handleMessage);
|
|
57
|
+
// return () => socket.removeEventListener("message", handleMessage);
|
|
58
|
+
// }, [socket, setMessages, selectedConversation, setSelectedConversation]);
|
|
18
59
|
|
|
19
60
|
// Join chat room when conversation is selected
|
|
61
|
+
|
|
20
62
|
useEffect(() => {
|
|
21
63
|
if (selectedConversation?._id && socket?.readyState === WebSocket.OPEN) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
64
|
+
// Send joinChat command to server via WebSocket
|
|
65
|
+
socket.send(
|
|
66
|
+
JSON.stringify({
|
|
67
|
+
event: "joinChat",
|
|
68
|
+
data: {
|
|
69
|
+
chatId: selectedConversation._id,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
);
|
|
30
73
|
}
|
|
31
|
-
}, [selectedConversation?._id, socket
|
|
74
|
+
}, [selectedConversation?._id, socket]);
|
|
32
75
|
|
|
33
|
-
// Listen for online users updates
|
|
34
76
|
useEffect(() => {
|
|
35
77
|
if (!socket) return;
|
|
36
78
|
|
|
37
79
|
const handleMessage = (event: MessageEvent) => {
|
|
38
80
|
try {
|
|
39
81
|
const data = JSON.parse(event.data);
|
|
40
|
-
|
|
41
|
-
|
|
82
|
+
console.log("Parsed WebSocket message in mc:", data);
|
|
83
|
+
|
|
84
|
+
if (data.event === "getOnlineUsers") {
|
|
85
|
+
setOnlineUsers(data.payload); // payload should be an array of user IDs
|
|
42
86
|
}
|
|
43
|
-
} catch (
|
|
44
|
-
console.error("
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error("Failed to parse WebSocket message", err);
|
|
45
89
|
}
|
|
46
90
|
};
|
|
47
91
|
|
|
@@ -52,17 +96,23 @@ const MessageContainer = () => {
|
|
|
52
96
|
};
|
|
53
97
|
}, [socket, setOnlineUsers]);
|
|
54
98
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
? selectedConversation?.participantDetails?.profilePic
|
|
59
|
-
: undefined;
|
|
99
|
+
// Listen for online users updates
|
|
100
|
+
|
|
101
|
+
const { userId } = useChatContext();
|
|
60
102
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
onlineUsers?.includes(selectedConversation.participantDetails._id);
|
|
103
|
+
// const participantDetails = Array.isArray(selectedConversation?.participantDetails)
|
|
104
|
+
// ? selectedConversation?.participantDetails
|
|
105
|
+
// : [selectedConversation?.participantDetails].filter(Boolean);
|
|
65
106
|
|
|
107
|
+
// const participant = participantDetails.find(
|
|
108
|
+
// (p: any) => p._id !== userId
|
|
109
|
+
// );
|
|
110
|
+
|
|
111
|
+
const participant = selectedConversation?.participantDetails?.find(
|
|
112
|
+
(p: any) => p._id !== userId
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const isOnline = isUserOnline(participant?._id || "");
|
|
66
116
|
|
|
67
117
|
// Cleanup on unmount
|
|
68
118
|
useEffect(() => {
|
|
@@ -80,38 +130,45 @@ const MessageContainer = () => {
|
|
|
80
130
|
<button className="chatMessageContainerInnerDiv_button">
|
|
81
131
|
{/* <CaretLeft size={25} /> */}
|
|
82
132
|
</button>
|
|
83
|
-
|
|
84
|
-
className="
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
133
|
+
{selectedConversation.type === "service" ? (
|
|
134
|
+
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center">
|
|
135
|
+
<MessageSquare size={24} className="text-gray-600" />
|
|
136
|
+
</div>
|
|
137
|
+
) : (
|
|
138
|
+
<img
|
|
139
|
+
className="chatMessageContainerInnerImg"
|
|
140
|
+
alt="Profile"
|
|
141
|
+
src={participant?.profilePic}
|
|
142
|
+
/>
|
|
143
|
+
)}
|
|
94
144
|
<div className="chatMessageContainerOutter">
|
|
95
145
|
<div className="chatMessageContainerOutterDiv">
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
146
|
+
{selectedConversation.type === "service" ? (
|
|
147
|
+
<>
|
|
148
|
+
<p className="chatMessageContainerOutterDiv_name">
|
|
149
|
+
{selectedConversation.serviceTitle}
|
|
150
|
+
</p>
|
|
151
|
+
<p className="text-sm">
|
|
152
|
+
Booking ID: #{selectedConversation.bookingId}
|
|
153
|
+
</p>
|
|
154
|
+
</>
|
|
155
|
+
) : (
|
|
156
|
+
<>
|
|
157
|
+
<p className="chatMessageContainerOutterDiv_name">
|
|
158
|
+
{participant?.firstname || ""}
|
|
159
|
+
</p>
|
|
160
|
+
<p className="text-sm">
|
|
161
|
+
{isOnline ? "Online" : "Offline"}
|
|
162
|
+
</p>
|
|
163
|
+
</>
|
|
164
|
+
)}
|
|
107
165
|
</div>
|
|
108
166
|
</div>
|
|
109
167
|
</div>
|
|
110
168
|
</div>
|
|
111
169
|
|
|
112
170
|
<Messages />
|
|
113
|
-
{role !==
|
|
114
|
-
|
|
171
|
+
{role !== "admin" && <MessageInput />}
|
|
115
172
|
</>
|
|
116
173
|
)}
|
|
117
174
|
</div>
|
|
@@ -213,4 +270,4 @@ const EmptyInbox: React.FC<EmptyInboxProps> = ({
|
|
|
213
270
|
<p className="text-gray-500 max-w-sm">{description}</p>
|
|
214
271
|
</div>
|
|
215
272
|
);
|
|
216
|
-
};
|
|
273
|
+
};
|