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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "l-min-components",
3
- "version": "1.0.1208",
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.id &&
377
- !latestMessageIdFromFetch.is_read
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.data) return;
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 end
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
- // await getIndividualChats(null, "5be9b48281ac4c2885d3b719654ed59d"); // only call to get individual chat
531
- await sendMessage({
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;