l-min-components 1.0.1205 → 1.0.1207

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.1205",
3
+ "version": "1.0.1207",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/assets",
@@ -13,6 +13,7 @@
13
13
  "preview": "vite preview"
14
14
  },
15
15
  "dependencies": {
16
+ "@reduxjs/toolkit": "^2.6.1",
16
17
  "@slate-serializers/html": "^2.1.2",
17
18
  "axios": "^1.4.0",
18
19
  "axios-hooks": "^4.0.0",
@@ -56,6 +57,7 @@
56
57
  "react-modal": "^3.16.1",
57
58
  "react-multi-carousel": "^2.8.5",
58
59
  "react-player": "^2.15.1",
60
+ "react-redux": "^9.2.0",
59
61
  "react-responsive-carousel": "^3.2.23",
60
62
  "react-router-dom": "^6.8.2",
61
63
  "react-slick": "0.30.2",
@@ -25,6 +25,7 @@ import useHeader from "../header/getHeaderDetails";
25
25
  import GracePeriod from "../deactivated";
26
26
  import MobileLayout from "../mobileLayout";
27
27
  import useTranslation from "../../hooks/useTranslation";
28
+ import useMessageKit from "../../hooks/messaging-kit";
28
29
 
29
30
  const AppMainLayout = ({ children }) => {
30
31
  const [isOpen, setIsOpen] = useState(true);
@@ -241,6 +242,8 @@ const AppMainLayout = ({ children }) => {
241
242
  setIsTranslationsLoading,
242
243
  } = useTranslation(wordBank);
243
244
 
245
+ useMessageKit(affiliatesActive, selectedAccount, affiliateAccount); // useMessageKit
246
+
244
247
  return (
245
248
  <OutletContext.Provider
246
249
  value={{
@@ -272,8 +275,7 @@ const AppMainLayout = ({ children }) => {
272
275
  setNewNotifications,
273
276
  notificationMarkReadData,
274
277
  handleGetNotificationMarkRead,
275
- }}
276
- >
278
+ }}>
277
279
  {/* display mobile layout on device width*/}
278
280
  {deviceWidth < 1200 ? (
279
281
  <MobileLayout findText={findText} />
@@ -285,8 +287,7 @@ const AppMainLayout = ({ children }) => {
285
287
  ) : (
286
288
  <Layout
287
289
  coming={coming}
288
- hasLayoutBackgroundImage={hasLayoutBackgroundImage}
289
- >
290
+ hasLayoutBackgroundImage={hasLayoutBackgroundImage}>
290
291
  <HeaderComponent
291
292
  setNewNotifications={setNewNotifications}
292
293
  findText={findText}
@@ -0,0 +1,328 @@
1
+ import useAxios from "axios-hooks";
2
+ import { useEffect, useState } from "react";
3
+
4
+ /**
5
+ * Represents the details of the last message in a chat room overview.
6
+ * @typedef {object} LastMessage
7
+ * @property {string} id - The unique identifier of the message.
8
+ * @property {string} text - The text content of the message.
9
+ * @property {object} media - Media attachments associated with the message (structure might vary based on actual usage).
10
+ * @property {string} created_at - ISO 8601 timestamp string of when the message was created.
11
+ */
12
+
13
+ /**
14
+ * Represents the profile picture details associated with a chat room overview.
15
+ * @typedef {object} ProfilePicture
16
+ * @property {string} url - The URL of the profile picture.
17
+ * @property {string} mimetype - The MIME type of the picture (e.g., 'image/png').
18
+ */
19
+
20
+ /**
21
+ * Represents an individual chat room within a course grouping (overview).
22
+ * @typedef {object} ChatRoom
23
+ * @property {string} id - The unique identifier for the chat room (UUID string).
24
+ * @property {string} account_id - The ID of the associated account (likely user or instructor).
25
+ * @property {string} profile_name - The display name for the chat room or the other participant.
26
+ * @property {string} account_type - The type of the associated account (e.g., 'INSTRUCTOR', 'STUDENT').
27
+ * @property {boolean} is_private - Indicates if the chat room is private (e.g., a direct message).
28
+ * @property {boolean} is_online - Indicates the online status associated with the room/participant.
29
+ * @property {boolean} pin_status - Indicates if this specific chat room is pinned by the current user.
30
+ * @property {string} created_at - ISO 8601 timestamp string of when the room was created.
31
+ * @property {number} unread_count - The number of unread messages in this specific room for the current user.
32
+ * @property {LastMessage | null} last_message - The last message object sent in the room, or null if no messages exist.
33
+ * @property {ProfilePicture | null} profile_picture - The profile picture object associated with the room/participant, or null if none exists.
34
+ */
35
+
36
+ /**
37
+ * Represents a grouping of chat rooms associated with a specific course.
38
+ * Corresponds to an element in the 'results' array from the API for room lists.
39
+ * @typedef {object} CourseChatGrouping
40
+ * @property {number} id - The unique identifier for the course grouping (likely the course ID).
41
+ * @property {string} name - The name of the course.
42
+ * @property {boolean} pin_status - Indicates if the overall course grouping is pinned by the current user.
43
+ * @property {number} total_unread_count - The aggregated total number of unread messages across all rooms listed in this grouping for the current user.
44
+ * @property {ChatRoom[]} rooms - An array of individual chat rooms associated with this course.
45
+ */
46
+
47
+ /**
48
+ * Information about the sender of a message within chat history.
49
+ * @typedef {object} SenderInfo
50
+ * @property {string} id - The unique identifier of the sender.
51
+ * @property {string} first_name - The first name of the sender.
52
+ */
53
+
54
+ /**
55
+ * Detailed information about a media attachment within chat history.
56
+ * @typedef {object} MediaDetails
57
+ * @property {string} id - Unique identifier for the media file.
58
+ * @property {string} url - URL to access the media file stream.
59
+ * @property {string} name - The original filename of the media.
60
+ * @property {number} size - The size of the media file in bytes.
61
+ * @property {string} duration - The duration of the media (e.g., "HH:MM:SS" format).
62
+ * @property {string} mimetype - The MIME type of the media file (e.g., 'audio/x-wav').
63
+ * @property {string | null} last_used - ISO 8601 timestamp indicating the last usage, or null.
64
+ * @property {string} created_at - ISO 8601 timestamp string of when the media was uploaded/created.
65
+ * @property {string} updated_at - ISO 8601 timestamp string of the last update to the media record.
66
+ * @property {number} course_count - Count related to course usage.
67
+ * @property {string | null} thumbnail_url - URL for a thumbnail image, or null.
68
+ * @property {string} convertion_status - Status of any media conversion process.
69
+ */
70
+
71
+ /**
72
+ * Represents a single chat message within chat history.
73
+ * @typedef {object} ChatMessage
74
+ * @property {string} id - The unique identifier for the message (UUID string).
75
+ * @property {string | null} text - The text content of the message. Null if only media.
76
+ * @property {MediaDetails | object | null} media - Media details, an empty object, or null.
77
+ * @property {SenderInfo} sender - Information about the message sender.
78
+ * @property {boolean} is_read - Read status flag.
79
+ * @property {boolean} is_delivered - Delivery status flag.
80
+ * @property {string} created_at - ISO 8601 timestamp string when the message was created.
81
+ * @property {boolean} user_message - True if sent by the current user, false otherwise.
82
+ * @property {string} date - Specific date (YYYY-MM-DD format) message was sent.
83
+ */
84
+
85
+ /**
86
+ * Represents a grouping of messages sent on a specific date within chat history.
87
+ * @typedef {object} MessagesByDate
88
+ * @property {string} date - Display string for the date group (e.g., "today", "Apr 04").
89
+ * @property {ChatMessage[]} messages - Array of message objects for this date group.
90
+ */
91
+
92
+ /**
93
+ * Represents the chat history structure for a single room, used as values in the state.chats object.
94
+ * @typedef {object} RoomChatHistory
95
+ * @property {MessagesByDate[]} results - An array containing messages grouped by date for the specific room.
96
+ */
97
+
98
+ /**
99
+ * Represents the expected structure of the API response data payload
100
+ * containing messages grouped by date.
101
+ * @typedef {object} MessageApiResponseData
102
+ * @property {number} count - Total number of items available across all pages (if pagination applies to items, not groups).
103
+ * @property {string | null} next - URL for the next page of results (date groups), or null.
104
+ * @property {string | null} previous - URL for the previous page of results (date groups), or null.
105
+ * @property {MessagesByDate[]} results - Array containing messages grouped by date.
106
+ */
107
+ const useMessageKit = (affiliatesActive, selectedAccount, affiliateAccount) => {
108
+ // get account type
109
+ const [, request] = useAxios(
110
+ {
111
+ method: "GET",
112
+ params: {
113
+ _account: selectedAccount && selectedAccount.id,
114
+ },
115
+ },
116
+ {
117
+ manual: true,
118
+ }
119
+ );
120
+
121
+ //get account type
122
+
123
+ // initialize message store
124
+
125
+ /**
126
+ * React state hook managing application chat data.
127
+ *
128
+ * The `state` object holds chat rooms grouped by courses (`roomsByCourses`)
129
+ * and the detailed message history for individual chat rooms (`chats`).
130
+ * The `setState` function is used to update this state.
131
+ *
132
+ * @type {[
133
+ * { roomsByCourses: CourseChatGrouping[], chats: Record<string, RoomChatHistory> },
134
+ * React.Dispatch<React.SetStateAction<{ roomsByCourses: CourseChatGrouping[], chats: Record<string, RoomChatHistory> }>>
135
+ * ]} stateTuple - A tuple containing the state object and the dispatcher function. Note the type for `chats`.
136
+ * @property {CourseChatGrouping[]} stateTuple[0].roomsByCourses - Chat rooms grouped by their associated course. Initialized as empty array.
137
+ * @property {Record<string, RoomChatHistory>} stateTuple[0].chats - An object mapping Room IDs (string keys) to their respective chat history (`RoomChatHistory` objects).
138
+ * Example structure: { "roomId123": { results: [...] }, "roomId456": { results: [...] } }
139
+ */
140
+ const [state, setState] = useState({
141
+ roomsByCourses: [],
142
+ chats: {},
143
+ });
144
+
145
+ // get room grouped by course list
146
+ const getMessageRoomsByCourses = async (nextPage) => {
147
+ if (!selectedAccount) return;
148
+ // selected account exists
149
+ let response;
150
+ if (String(selectedAccount.type).toLowerCase() === "enterprise") {
151
+ response = await request({
152
+ url: nextPage || "/notify/v1/enterprise/rooms/",
153
+ });
154
+ }
155
+ if (
156
+ String(selectedAccount.type).toLowerCase() === "instructor" &&
157
+ affiliateAccount
158
+ ) {
159
+ response = await request({
160
+ url: nextPage || "/notify/v1/instructor/:enterprise_id/rooms/",
161
+ });
162
+ } // incomplete
163
+ if (String(selectedAccount.type).toLowerCase() === "personal") {
164
+ response = await request({
165
+ url: nextPage || "/notify/v1/rooms/",
166
+ });
167
+ } // incomplete
168
+ if (!response.data) return;
169
+ //found data
170
+
171
+ const { results: newCourseGroupings, next } = response.data;
172
+ setState((prevState) => {
173
+ const combined = [...prevState.roomsByCourses, ...newCourseGroupings];
174
+ // Use a Map to filter out duplicates based on the course grouping 'id'
175
+ const uniqueMap = new Map(combined.map((item) => [item.id, item]));
176
+ const uniqueCourseGroupings = Array.from(uniqueMap.values());
177
+
178
+ return {
179
+ ...prevState,
180
+ roomsByCourses: uniqueCourseGroupings,
181
+ };
182
+ });
183
+ if (next) {
184
+ // Ensure the correct function name is called recursively
185
+ getMessageRoomsByCourses(next);
186
+ }
187
+ };
188
+
189
+ // get individual chats
190
+ const getIndividualChats = async (nextPage, roomId) => {
191
+ if (!selectedAccount) return;
192
+
193
+ let response;
194
+ if (String(selectedAccount.type).toLowerCase() === "enterprise") {
195
+ response = await request({
196
+ url: nextPage || "/notify/v1/enterprise/chats/",
197
+ params: {
198
+ _account: selectedAccount.id,
199
+ room_id: roomId,
200
+ },
201
+ });
202
+ }
203
+
204
+ if (!response.data) return;
205
+
206
+ /** @type {MessageApiResponseData} */
207
+ const responseData = response.data;
208
+ const { results: newDateGroups } = responseData;
209
+
210
+ setState((prevState) => {
211
+ let unreadCountToSubtract = 0;
212
+
213
+ // --- Update roomsByCourses ---
214
+ const updatedRoomsByCourses = prevState.roomsByCourses.map(
215
+ (courseGroup) => {
216
+ // Find the room within this course group
217
+ const roomIndex = courseGroup.rooms.findIndex(
218
+ (room) => room.id === roomId
219
+ );
220
+
221
+ if (roomIndex !== -1) {
222
+ // Found the room in this course group
223
+ const targetRoom = courseGroup.rooms[roomIndex];
224
+ unreadCountToSubtract = targetRoom.unread_count; // Store the count before resetting
225
+
226
+ // Create a new rooms array with the updated room
227
+ const updatedRooms = [
228
+ ...courseGroup.rooms.slice(0, roomIndex),
229
+ { ...targetRoom, unread_count: 0 }, // Reset unread count for the specific room
230
+ ...courseGroup.rooms.slice(roomIndex + 1),
231
+ ];
232
+
233
+ // Return a new course group object with updated total count and rooms
234
+ return {
235
+ ...courseGroup,
236
+ // Ensure total_unread_count doesn't go below zero
237
+ total_unread_count: Math.max(
238
+ 0,
239
+ courseGroup.total_unread_count - unreadCountToSubtract
240
+ ),
241
+ rooms: updatedRooms,
242
+ };
243
+ }
244
+
245
+ // If the room wasn't in this group, return the group unchanged
246
+ return courseGroup;
247
+ }
248
+ );
249
+ // --- End Update roomsByCourses ---
250
+
251
+ // --- Update chats (existing logic) ---
252
+ const existingDateGroups = prevState.chats[roomId]?.results || [];
253
+ const allDateGroups = [...existingDateGroups, ...newDateGroups];
254
+
255
+ // Group messages by date and de-duplicate messages within each date using their ID
256
+ const messagesGroupedByDate = {};
257
+ allDateGroups.forEach((dateGroup) => {
258
+ if (!messagesGroupedByDate[dateGroup.date]) {
259
+ // Use a Map for efficient de-duplication by message ID
260
+ messagesGroupedByDate[dateGroup.date] = {
261
+ date: dateGroup.date, // Store the date string
262
+ messagesMap: new Map(),
263
+ };
264
+ }
265
+ dateGroup.messages.forEach((message) => {
266
+ messagesGroupedByDate[dateGroup.date].messagesMap.set(
267
+ message.id,
268
+ message
269
+ );
270
+ });
271
+ });
272
+
273
+ // Reconstruct the final array of date groups with unique messages
274
+ const finalDateGroups = Object.values(messagesGroupedByDate).map(
275
+ (group) => ({
276
+ date: group.date,
277
+ // Convert Map values back to an array and sort messages by creation time
278
+ messages: Array.from(group.messagesMap.values()).sort(
279
+ (a, b) => new Date(a.created_at) - new Date(b.created_at)
280
+ ),
281
+ })
282
+ );
283
+
284
+ // Optional: Sort the date groups themselves if needed (e.g., chronologically)
285
+ // finalDateGroups.sort((a, b) => /* comparison logic based on date */);
286
+
287
+ // --- Return combined state update ---
288
+ return {
289
+ ...prevState,
290
+ roomsByCourses: updatedRoomsByCourses, // Include the updated course/room list
291
+ chats: {
292
+ ...prevState.chats,
293
+ // Update the structure to match RoomChatHistory: { results: [...] }
294
+ [roomId]: {
295
+ // Preserve potential other properties if RoomChatHistory expands later
296
+ ...(prevState.chats[roomId] || {}),
297
+ results: finalDateGroups,
298
+ },
299
+ },
300
+ };
301
+ });
302
+ !nextPage && (await readRoomMessages(roomId));
303
+ // Pass roomId correctly in the recursive call
304
+ if (responseData.next) getIndividualChats(responseData.next, roomId);
305
+ };
306
+
307
+ // read messages
308
+ const readRoomMessages = async (roomId) => {
309
+ return await request({
310
+ url: `/notify/v1/enterprise/chats/${roomId}/read_message/`,
311
+ account_id: selectedAccount.id,
312
+ });
313
+ };
314
+ useEffect(() => {
315
+ (async () => {
316
+ await getMessageRoomsByCourses();
317
+ await getIndividualChats(null, "5be9b48281ac4c2885d3b719654ed59d"); // only call to get individual chat
318
+ })();
319
+ }, [selectedAccount]);
320
+
321
+ console.log(state, "messages"); // Keep console log for debugging if needed
322
+ // send messages to account based on account type
323
+
324
+ // Return the state and potentially the functions if they need to be called externally
325
+ return { state, getIndividualChats, getMessageRoomsByCourses };
326
+ };
327
+
328
+ export default useMessageKit;
@@ -0,0 +1,20 @@
1
+ import { createSlice } from "@reduxjs/toolkit";
2
+
3
+ const initialState = {
4
+ messageList: [],
5
+ chats: [],
6
+ };
7
+
8
+ const messageSlice = createSlice({
9
+ name: "message",
10
+ initialState,
11
+ reducers: {
12
+ setMessage: (state, action) => {
13
+ state.message = action.payload;
14
+ },
15
+ },
16
+ });
17
+
18
+ export const { setMessage } = messageSlice.actions;
19
+
20
+ export default messageSlice.reducer;