@pubuduth-aplicy/chat-ui 2.1.70 → 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 +275 -172
- 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 +147 -64
- 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 +110 -24
- package/src/types/type.ts +6 -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;
|
|
@@ -1559,4 +1566,83 @@ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe U
|
|
|
1559
1566
|
.cancel-edit {
|
|
1560
1567
|
background-color: #f44336;
|
|
1561
1568
|
color: white;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
.system-message.booking-details {
|
|
1572
|
+
display: flex;
|
|
1573
|
+
flex-direction: column;
|
|
1574
|
+
align-items: center;
|
|
1575
|
+
justify-content: center;
|
|
1576
|
+
max-width: 400px;
|
|
1577
|
+
margin: 20px auto;
|
|
1578
|
+
padding: 24px;
|
|
1579
|
+
background: #f8f9fa;
|
|
1580
|
+
border: 1px solid #e9ecef;
|
|
1581
|
+
border-radius: 12px;
|
|
1582
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
1583
|
+
text-align: center;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
.system-message.booking-details h4 {
|
|
1587
|
+
margin: 0 0 16px 0;
|
|
1588
|
+
font-size: 18px;
|
|
1589
|
+
font-weight: 600;
|
|
1590
|
+
color: #2c3e50;
|
|
1591
|
+
display: flex;
|
|
1592
|
+
align-items: center;
|
|
1593
|
+
gap: 8px;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
.system-message.booking-details h4::before {
|
|
1597
|
+
content: "✓";
|
|
1598
|
+
display: inline-block;
|
|
1599
|
+
width: 24px;
|
|
1600
|
+
height: 24px;
|
|
1601
|
+
background: #2cb1aa;
|
|
1602
|
+
color: white;
|
|
1603
|
+
border-radius: 50%;
|
|
1604
|
+
font-size: 14px;
|
|
1605
|
+
line-height: 24px;
|
|
1606
|
+
text-align: center;
|
|
1607
|
+
font-weight: bold;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
.system-message.booking-details .details {
|
|
1611
|
+
width: 100%;
|
|
1612
|
+
background: white;
|
|
1613
|
+
border-radius: 8px;
|
|
1614
|
+
padding: 16px;
|
|
1615
|
+
border: 1px solid #dee2e6;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
.system-message.booking-details .details p {
|
|
1619
|
+
margin: 8px 0;
|
|
1620
|
+
padding: 8px 0;
|
|
1621
|
+
border-bottom: 1px solid #f1f3f4;
|
|
1622
|
+
font-size: 14px;
|
|
1623
|
+
color: #495057;
|
|
1624
|
+
display: flex;
|
|
1625
|
+
justify-content: space-between;
|
|
1626
|
+
align-items: center;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
.system-message.booking-details .details p:last-child {
|
|
1630
|
+
border-bottom: none;
|
|
1631
|
+
font-weight: 600;
|
|
1632
|
+
color: #2cb1aa;
|
|
1633
|
+
font-size: 16px;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
.system-message.booking-details .details p strong {
|
|
1637
|
+
color: #2c3e50;
|
|
1638
|
+
font-weight: 500;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/* Responsive design */
|
|
1642
|
+
@media (max-width: 480px) {
|
|
1643
|
+
.system-message.booking-details {
|
|
1644
|
+
max-width: 90%;
|
|
1645
|
+
margin: 16px auto;
|
|
1646
|
+
padding: 20px;
|
|
1647
|
+
}
|
|
1562
1648
|
}
|
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,11 +47,13 @@ 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[];
|
|
54
|
+
media: string[];
|
|
55
|
+
type?: 'user' | 'system' | 'system-completion';
|
|
56
|
+
status: MessageStatus;
|
|
55
57
|
chatId: string;
|
|
56
58
|
createdAt: string;
|
|
57
59
|
updatedAt: string;
|
|
@@ -70,4 +72,4 @@ export interface ConversationProps {
|
|
|
70
72
|
lastIdx: boolean;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
export type MessageStatus = 'sent' | 'delivered' | 'read' | 'sending' | 'failed';
|
|
75
|
+
export type MessageStatus = 'sent' | 'delivered' | 'read' | 'sending' | 'failed' | 'edited' | 'deleted';
|