l-min-components 1.0.1208 → 1.0.1209
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/hooks/messaging-kit/index.jsx +230 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "l-min-components",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1209",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src/assets",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"react-slick": "0.30.2",
|
|
64
64
|
"react-toastify": "^9.1.3",
|
|
65
65
|
"react-tooltip": "^5.10.1",
|
|
66
|
+
"react-use-websocket": "^4.13.0",
|
|
66
67
|
"screenfull": "^6.0.2",
|
|
67
68
|
"slate": "^0.94.0",
|
|
68
69
|
"slate-history": "^0.93.0",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import useAxios from "axios-hooks";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
+
import useWebSocket, { ReadyState } from "react-use-websocket";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Represents the details of the last message in a chat room overview.
|
|
@@ -163,7 +164,83 @@ import { useEffect, useState } from "react";
|
|
|
163
164
|
* @property {string} created_at - ISO 8601 timestamp string when the message was created on the server.
|
|
164
165
|
*/
|
|
165
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Represents the profile picture details within the sender object of a WebSocket message event.
|
|
169
|
+
* Note: This structure might be simpler than SenderProfilePicture.
|
|
170
|
+
* @typedef {object} WebSocketSenderProfilePicture
|
|
171
|
+
* @property {string} url - URL to access the profile picture.
|
|
172
|
+
* @property {string} mimetype - The MIME type of the picture file (e.g., 'image/png').
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Represents the sender details within a WebSocket message event data.
|
|
177
|
+
* @typedef {object} WebSocketSender
|
|
178
|
+
* @property {string} id - The unique identifier of the sender.
|
|
179
|
+
* @property {string} name - The name of the sender.
|
|
180
|
+
* @property {string} full_name - The full name of the sender.
|
|
181
|
+
* @property {object} cover_photo - Cover photo details (currently an empty object in the example).
|
|
182
|
+
* @property {WebSocketSenderProfilePicture} profile_picture - Profile picture information for the sender.
|
|
183
|
+
* @property {string | null} bio - The sender's biography text, or null.
|
|
184
|
+
*/
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Represents the media attachment details within a WebSocket message event data.
|
|
188
|
+
* Note: This is an empty object in the example, structure might vary.
|
|
189
|
+
* @typedef {object | SentMessageMedia | null} WebSocketMedia - Can be an empty object, structured media details, or null.
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Represents the data payload of a WebSocket message event (e.g., 'new.message.result').
|
|
194
|
+
* @typedef {object} WebSocketMessageData
|
|
195
|
+
* @property {string} id - The unique identifier for the message (UUID string).
|
|
196
|
+
* @property {boolean} user_message - Indicates if the message was sent by the *currently authenticated* user.
|
|
197
|
+
* @property {WebSocketSender} sender - Detailed information about the message sender.
|
|
198
|
+
* @property {string} room - The unique identifier of the chat room the message belongs to.
|
|
199
|
+
* @property {string | null} text - The text content of the message, or null if only media.
|
|
200
|
+
* @property {WebSocketMedia} media - Details of the attached media (can be empty object or structured).
|
|
201
|
+
* @property {boolean} is_read - Read status flag.
|
|
202
|
+
* @property {boolean} is_delivered - Delivery status flag.
|
|
203
|
+
* @property {string} status - The status of the message (e.g., 'active').
|
|
204
|
+
* @property {string} created_at - ISO 8601 timestamp string when the message was created.
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Represents the overall structure of a WebSocket message event.
|
|
209
|
+
* @typedef {object} WebSocketMessageEvent
|
|
210
|
+
* @property {string} event - The type of the event (e.g., 'new.message.result').
|
|
211
|
+
* @property {WebSocketMessageData} data - The payload containing the message details.
|
|
212
|
+
*/
|
|
213
|
+
|
|
166
214
|
const useMessageKit = (affiliatesActive, selectedAccount, affiliateAccount) => {
|
|
215
|
+
const buildSocketUrl = () => {
|
|
216
|
+
if (!selectedAccount) return;
|
|
217
|
+
const baseUrl =
|
|
218
|
+
window.location.hostname.includes("staging") ||
|
|
219
|
+
window.location.hostname.includes("localhost")
|
|
220
|
+
? "https://dev-117782726-api.learngual.com"
|
|
221
|
+
: "https://api.learngual.com";
|
|
222
|
+
const cookies = document.cookie;
|
|
223
|
+
const token = cookies.split(";").find((cookie) => {
|
|
224
|
+
cookie = cookie.trim();
|
|
225
|
+
if (cookie.startsWith("access=")) return true;
|
|
226
|
+
});
|
|
227
|
+
return (
|
|
228
|
+
baseUrl.replace("http", "ws") +
|
|
229
|
+
`/notify/v1/ws/connect/?authorization=${token.split("=")[1]}&_account=${
|
|
230
|
+
selectedAccount.id
|
|
231
|
+
}`
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// handle socket connection
|
|
236
|
+
const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket(
|
|
237
|
+
buildSocketUrl(),
|
|
238
|
+
{
|
|
239
|
+
share: false,
|
|
240
|
+
shouldReconnect: () => true,
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
|
|
167
244
|
// get account type
|
|
168
245
|
const [, request] = useAxios(
|
|
169
246
|
{
|
|
@@ -373,10 +450,10 @@ const useMessageKit = (affiliatesActive, selectedAccount, affiliateAccount) => {
|
|
|
373
450
|
// Call readRoomMessages only on the first page fetch, if a latest message ID was found and is its not read
|
|
374
451
|
if (
|
|
375
452
|
!nextPage &&
|
|
376
|
-
latestMessageIdFromFetch
|
|
377
|
-
!latestMessageIdFromFetch
|
|
453
|
+
latestMessageIdFromFetch?.id && // Optional chaining for safety
|
|
454
|
+
!latestMessageIdFromFetch?.is_read // Optional chaining for safety
|
|
378
455
|
) {
|
|
379
|
-
await readRoomMessages(latestMessageIdFromFetch);
|
|
456
|
+
await readRoomMessages(latestMessageIdFromFetch.id); // Pass only the ID
|
|
380
457
|
}
|
|
381
458
|
|
|
382
459
|
// Pass roomId correctly in the recursive call
|
|
@@ -412,7 +489,7 @@ const useMessageKit = (affiliatesActive, selectedAccount, affiliateAccount) => {
|
|
|
412
489
|
});
|
|
413
490
|
}
|
|
414
491
|
|
|
415
|
-
if (!response
|
|
492
|
+
if (!response?.data) return; // Added optional chaining for safety
|
|
416
493
|
|
|
417
494
|
/** @type {SentMessageResponse} */
|
|
418
495
|
const sentMessageData = response.data;
|
|
@@ -470,7 +547,7 @@ const useMessageKit = (affiliatesActive, selectedAccount, affiliateAccount) => {
|
|
|
470
547
|
// Replace the old group with the updated one
|
|
471
548
|
updatedChatHistory.splice(todayGroupIndex, 1, updatedTodayGroup);
|
|
472
549
|
} else {
|
|
473
|
-
// "today" group doesn't exist, create it and add to the
|
|
550
|
+
// "today" group doesn't exist, create it and add to the beginning
|
|
474
551
|
const newTodayGroup = {
|
|
475
552
|
date: todayDateString,
|
|
476
553
|
messages: [newChatMessage],
|
|
@@ -524,23 +601,163 @@ const useMessageKit = (affiliatesActive, selectedAccount, affiliateAccount) => {
|
|
|
524
601
|
// await readRoomMessages(newChatMessage.id); // Depends on desired logic
|
|
525
602
|
};
|
|
526
603
|
|
|
604
|
+
// Initial data fetch effect
|
|
527
605
|
useEffect(() => {
|
|
606
|
+
if (!selectedAccount) return;
|
|
528
607
|
(async () => {
|
|
529
608
|
await getMessageRoomsByCourses();
|
|
530
|
-
//
|
|
531
|
-
await
|
|
532
|
-
text: "this is an automated message 3!",
|
|
533
|
-
courseId: "878",
|
|
534
|
-
accountId: "24dbaeede1",
|
|
535
|
-
});
|
|
609
|
+
// Example: Fetch initial chat for a specific room if needed on load
|
|
610
|
+
// await getIndividualChats(null, "some-initial-room-id");
|
|
536
611
|
})();
|
|
537
|
-
}, [selectedAccount]);
|
|
612
|
+
}, [selectedAccount]); // Rerun when selectedAccount changes
|
|
613
|
+
|
|
614
|
+
// Effect to log incoming WebSocket messages (for debugging)
|
|
615
|
+
useEffect(() => {
|
|
616
|
+
console.log(`Got a new message: ${JSON.stringify(lastJsonMessage)}`);
|
|
617
|
+
}, [lastJsonMessage]);
|
|
618
|
+
|
|
619
|
+
// --- Handle Incoming WebSocket Messages ---
|
|
620
|
+
useEffect(() => {
|
|
621
|
+
if (lastJsonMessage && lastJsonMessage.event === "new.message.result") {
|
|
622
|
+
/** @type {WebSocketMessageData} */
|
|
623
|
+
const webSocketData = lastJsonMessage.data;
|
|
624
|
+
const roomId = webSocketData.room;
|
|
625
|
+
|
|
626
|
+
// Helper to get YYYY-MM-DD from ISO string (duplicate from sendMessage, consider extracting)
|
|
627
|
+
const getLocalDateString = (isoTimestamp) => {
|
|
628
|
+
if (!isoTimestamp) return new Date().toISOString().split("T")[0];
|
|
629
|
+
try {
|
|
630
|
+
return new Date(isoTimestamp).toISOString().split("T")[0];
|
|
631
|
+
} catch (e) {
|
|
632
|
+
console.error("Error parsing date:", e);
|
|
633
|
+
return new Date().toISOString().split("T")[0];
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
// --- Check if room exists in current state ---
|
|
638
|
+
let roomExists = false;
|
|
639
|
+
for (const courseGroup of state.roomsByCourses) {
|
|
640
|
+
if (courseGroup.rooms.some((room) => room.id === roomId)) {
|
|
641
|
+
roomExists = true;
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// --- If room doesn't exist, refetch room list and skip state update for this message ---
|
|
647
|
+
if (!roomExists) {
|
|
648
|
+
console.log(`Room ${roomId} not found in state, refetching room list.`);
|
|
649
|
+
getMessageRoomsByCourses(null, true); // Refetch rooms once
|
|
650
|
+
return; // Stop processing this message until room list is updated
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// --- Format WebSocket data into ChatMessage ---
|
|
654
|
+
/** @type {ChatMessage} */
|
|
655
|
+
const newChatMessage = {
|
|
656
|
+
id: webSocketData.id,
|
|
657
|
+
text: webSocketData.text,
|
|
658
|
+
// Map media - assuming WebSocketMedia is compatible enough
|
|
659
|
+
media: webSocketData.media,
|
|
660
|
+
sender: {
|
|
661
|
+
id: webSocketData.sender.id,
|
|
662
|
+
first_name:
|
|
663
|
+
webSocketData.sender.full_name || webSocketData.sender.name,
|
|
664
|
+
},
|
|
665
|
+
is_read: webSocketData.is_read,
|
|
666
|
+
is_delivered: webSocketData.is_delivered,
|
|
667
|
+
created_at: webSocketData.created_at,
|
|
668
|
+
user_message: webSocketData.user_message, // Crucial for unread count
|
|
669
|
+
date: getLocalDateString(webSocketData.created_at),
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
// --- Update State ---
|
|
673
|
+
setState((prevState) => {
|
|
674
|
+
// --- Update chats state ---
|
|
675
|
+
const currentChatHistory = prevState.chats[roomId] || [];
|
|
676
|
+
let updatedChatHistory = [...currentChatHistory];
|
|
677
|
+
const todayDateString = "today"; // Or derive actual date string
|
|
678
|
+
|
|
679
|
+
const todayGroupIndex = updatedChatHistory.findIndex(
|
|
680
|
+
(group) => group.date === todayDateString
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
if (todayGroupIndex !== -1) {
|
|
684
|
+
const todayGroup = updatedChatHistory[todayGroupIndex];
|
|
685
|
+
// Avoid adding duplicate messages if WS sends what we already added via sendMessage response
|
|
686
|
+
if (
|
|
687
|
+
!todayGroup.messages.some((msg) => msg.id === newChatMessage.id)
|
|
688
|
+
) {
|
|
689
|
+
const updatedTodayGroup = {
|
|
690
|
+
...todayGroup,
|
|
691
|
+
messages: [...todayGroup.messages, newChatMessage],
|
|
692
|
+
};
|
|
693
|
+
updatedChatHistory.splice(todayGroupIndex, 1, updatedTodayGroup);
|
|
694
|
+
}
|
|
695
|
+
} else {
|
|
696
|
+
const newTodayGroup = {
|
|
697
|
+
date: todayDateString,
|
|
698
|
+
messages: [newChatMessage],
|
|
699
|
+
};
|
|
700
|
+
updatedChatHistory = [newTodayGroup, ...updatedChatHistory];
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const updatedChats = {
|
|
704
|
+
...prevState.chats,
|
|
705
|
+
[roomId]: updatedChatHistory,
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// --- Update roomsByCourses state ---
|
|
709
|
+
const isIncomingMessage = !newChatMessage.user_message;
|
|
710
|
+
const updatedRoomsByCourses = prevState.roomsByCourses.map(
|
|
711
|
+
(courseGroup) => {
|
|
712
|
+
let courseUnreadIncrement = 0;
|
|
713
|
+
const roomIndex = courseGroup.rooms.findIndex(
|
|
714
|
+
(room) => room.id === roomId
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
if (roomIndex !== -1) {
|
|
718
|
+
const updatedRooms = [...courseGroup.rooms];
|
|
719
|
+
const currentRoom = updatedRooms[roomIndex];
|
|
720
|
+
const roomUnreadIncrement = isIncomingMessage ? 1 : 0;
|
|
721
|
+
courseUnreadIncrement = roomUnreadIncrement; // Track increment for the course total
|
|
722
|
+
|
|
723
|
+
updatedRooms[roomIndex] = {
|
|
724
|
+
...currentRoom,
|
|
725
|
+
last_message: {
|
|
726
|
+
id: newChatMessage.id,
|
|
727
|
+
text: newChatMessage.text,
|
|
728
|
+
media: newChatMessage.media,
|
|
729
|
+
created_at: newChatMessage.created_at,
|
|
730
|
+
},
|
|
731
|
+
// Increment unread count only for incoming messages
|
|
732
|
+
unread_count: currentRoom.unread_count + roomUnreadIncrement,
|
|
733
|
+
};
|
|
734
|
+
return {
|
|
735
|
+
...courseGroup,
|
|
736
|
+
rooms: updatedRooms,
|
|
737
|
+
// Increment total unread count for the course group
|
|
738
|
+
total_unread_count:
|
|
739
|
+
courseGroup.total_unread_count + courseUnreadIncrement,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
return courseGroup; // Return unchanged if room not in this group
|
|
743
|
+
}
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
// --- Return combined state update ---
|
|
747
|
+
return {
|
|
748
|
+
...prevState,
|
|
749
|
+
chats: updatedChats,
|
|
750
|
+
roomsByCourses: updatedRoomsByCourses,
|
|
751
|
+
};
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
}, [lastJsonMessage, state.roomsByCourses]); // Also depend on roomsByCourses to re-check existence after refetch
|
|
538
755
|
|
|
539
756
|
console.log(state, "messages"); // Keep console log for debugging if needed
|
|
540
757
|
// send messages to account based on account type
|
|
541
758
|
|
|
542
759
|
// Return the state and potentially the functions if they need to be called externally
|
|
543
|
-
return { state, getIndividualChats, getMessageRoomsByCourses };
|
|
760
|
+
return { state, getIndividualChats, getMessageRoomsByCourses, sendMessage }; // Ensure sendMessage is returned
|
|
544
761
|
};
|
|
545
762
|
|
|
546
763
|
export default useMessageKit;
|