l-min-components 1.6.1265 → 1.6.1266
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/AppMainLayout/index.jsx +34 -11
- package/src/components/header/account-dropdown.jsx +5 -10
- package/src/components/header/getHeaderDetails.js +6 -1
- package/src/components/header/index.jsx +9 -17
- package/src/components/instructorAccountSwitcher/index.jsx +17 -19
- package/src/components/sideBar/userCard/index.jsx +7 -11
- package/src/hooks/messaging-kit/index.jsx +73 -30
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect, createContext } from "react";
|
|
1
|
+
import React, { useState, useEffect, createContext, useMemo } from "react";
|
|
2
2
|
import { Outlet, useLocation } from "react-router-dom";
|
|
3
3
|
import {
|
|
4
4
|
Layout,
|
|
@@ -42,13 +42,11 @@ const AppMainLayout = ({ children }) => {
|
|
|
42
42
|
const [sideMenuLayout, setSideMenuLayout] = useState(true);
|
|
43
43
|
const [defaultAcct, setDefaultAcct] = useState("");
|
|
44
44
|
const [centerLayoutStyle, setCenterLayoutStyle] = useState({});
|
|
45
|
-
const [hideAffilicates, setHideAffilicates] = useState(false);
|
|
46
45
|
const [affiliatesActive, setAffiliatesActive] = useState(false);
|
|
47
46
|
const [accessToken, setAccessToken] = useState("");
|
|
48
47
|
const [envType, setEnvType] = useState("");
|
|
49
48
|
const [newNotifications, setNewNotifications] = useState([]);
|
|
50
49
|
const [affiliateAccount, setAffiliateAccount] = useState(null);
|
|
51
|
-
const [deactivated, setDeactivated] = useState(true); // testing, until we get account setup
|
|
52
50
|
const [gracePeriod, setGracePeriod] = useState(true); // test
|
|
53
51
|
const [deviceWidth, setDeviceWidth] = useState(window.innerWidth);
|
|
54
52
|
const [newLoadingForPostDefaultAccount, setNewLoadingForPostDefaultAccount] =
|
|
@@ -69,6 +67,34 @@ const AppMainLayout = ({ children }) => {
|
|
|
69
67
|
);
|
|
70
68
|
}, []);
|
|
71
69
|
|
|
70
|
+
// getting account name
|
|
71
|
+
// active account type
|
|
72
|
+
const activeAccountType = useMemo(
|
|
73
|
+
() => String(generalData?.selectedAccount?.type).toLowerCase(),
|
|
74
|
+
[generalData]
|
|
75
|
+
);
|
|
76
|
+
const accountName = useMemo(() => {
|
|
77
|
+
const { user: User, selectedAccount } = generalData;
|
|
78
|
+
if (!User || !selectedAccount) return "..."; // or an empty string
|
|
79
|
+
const defaultAccountName = String(User.first_name).concat(
|
|
80
|
+
" ",
|
|
81
|
+
User.last_name
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
switch (activeAccountType) {
|
|
85
|
+
case "enterprise":
|
|
86
|
+
return selectedAccount.metadata.organization_name ?? defaultAccountName;
|
|
87
|
+
case "developer":
|
|
88
|
+
return (
|
|
89
|
+
selectedAccount.metadata.organization_name ??
|
|
90
|
+
selectedAccount.metadata.org_name ??
|
|
91
|
+
defaultAccountName
|
|
92
|
+
);
|
|
93
|
+
default:
|
|
94
|
+
return defaultAccountName;
|
|
95
|
+
}
|
|
96
|
+
}, [generalData]);
|
|
97
|
+
console.log(activeAccountType, "activeAccountType");
|
|
72
98
|
const {
|
|
73
99
|
setDefaultAccount,
|
|
74
100
|
handleSetDefaultAccount,
|
|
@@ -119,8 +145,8 @@ const AppMainLayout = ({ children }) => {
|
|
|
119
145
|
if (setDefaultAccount?.data) {
|
|
120
146
|
if (
|
|
121
147
|
setDefaultAccount?.response?.status === 207 &&
|
|
122
|
-
|
|
123
|
-
|
|
148
|
+
!window.location.host.includes("staging") &&
|
|
149
|
+
!window.location.host.includes("local")
|
|
124
150
|
) {
|
|
125
151
|
window.location.href = "/auth/account-type";
|
|
126
152
|
} else {
|
|
@@ -253,6 +279,7 @@ const AppMainLayout = ({ children }) => {
|
|
|
253
279
|
value={{
|
|
254
280
|
messageKit,
|
|
255
281
|
affiliateAccount,
|
|
282
|
+
accountName,
|
|
256
283
|
setRightComponent,
|
|
257
284
|
setRightLayout,
|
|
258
285
|
generalData,
|
|
@@ -271,8 +298,6 @@ const AppMainLayout = ({ children }) => {
|
|
|
271
298
|
setSelectedCourseId,
|
|
272
299
|
centerLayoutStyle,
|
|
273
300
|
setCenterLayoutStyle,
|
|
274
|
-
// return true if instructor affiliates is Active
|
|
275
|
-
setHideAffilicates,
|
|
276
301
|
affiliatesActive,
|
|
277
302
|
accessToken,
|
|
278
303
|
envType,
|
|
@@ -280,8 +305,7 @@ const AppMainLayout = ({ children }) => {
|
|
|
280
305
|
setNewNotifications,
|
|
281
306
|
notificationMarkReadData,
|
|
282
307
|
handleGetNotificationMarkRead,
|
|
283
|
-
}}
|
|
284
|
-
>
|
|
308
|
+
}}>
|
|
285
309
|
{/* display mobile layout on device width*/}
|
|
286
310
|
{deviceWidth < 1200 ? (
|
|
287
311
|
<MobileLayout findText={findText} />
|
|
@@ -293,8 +317,7 @@ const AppMainLayout = ({ children }) => {
|
|
|
293
317
|
) : (
|
|
294
318
|
<Layout
|
|
295
319
|
coming={coming}
|
|
296
|
-
hasLayoutBackgroundImage={hasLayoutBackgroundImage}
|
|
297
|
-
>
|
|
320
|
+
hasLayoutBackgroundImage={hasLayoutBackgroundImage}>
|
|
298
321
|
<HeaderComponent
|
|
299
322
|
setNewNotifications={setNewNotifications}
|
|
300
323
|
findText={findText}
|
|
@@ -132,8 +132,7 @@ const AccountDropdown = (props) => {
|
|
|
132
132
|
handleRedirectFunc(developerItem);
|
|
133
133
|
}, 1000);
|
|
134
134
|
}}
|
|
135
|
-
key={idx}
|
|
136
|
-
>
|
|
135
|
+
key={idx}>
|
|
137
136
|
<div className="avatar-container">
|
|
138
137
|
<img
|
|
139
138
|
src={
|
|
@@ -179,8 +178,7 @@ const AccountDropdown = (props) => {
|
|
|
179
178
|
handleRedirectFunc(instructorItem);
|
|
180
179
|
}, 1000);
|
|
181
180
|
}}
|
|
182
|
-
key={idx}
|
|
183
|
-
>
|
|
181
|
+
key={idx}>
|
|
184
182
|
<div className="avatar-container">
|
|
185
183
|
<img
|
|
186
184
|
src={
|
|
@@ -221,8 +219,7 @@ const AccountDropdown = (props) => {
|
|
|
221
219
|
handleRedirectFunc(enterpriseItem);
|
|
222
220
|
}, 1000);
|
|
223
221
|
}}
|
|
224
|
-
key={idx}
|
|
225
|
-
>
|
|
222
|
+
key={idx}>
|
|
226
223
|
<div className="avatar-container">
|
|
227
224
|
<img
|
|
228
225
|
src={
|
|
@@ -266,8 +263,7 @@ const AccountDropdown = (props) => {
|
|
|
266
263
|
handleRedirectFunc(personalItem);
|
|
267
264
|
}, 1000);
|
|
268
265
|
}}
|
|
269
|
-
key={idx}
|
|
270
|
-
>
|
|
266
|
+
key={idx}>
|
|
271
267
|
<div className="avatar-container">
|
|
272
268
|
<img
|
|
273
269
|
src={
|
|
@@ -301,8 +297,7 @@ const AccountDropdown = (props) => {
|
|
|
301
297
|
onClick={() => {
|
|
302
298
|
window.location.href = "/auth/account-type";
|
|
303
299
|
}}
|
|
304
|
-
style={{ cursor: "pointer" }}
|
|
305
|
-
>
|
|
300
|
+
style={{ cursor: "pointer" }}>
|
|
306
301
|
<AddIcon /> {props.findText("Add account")}
|
|
307
302
|
</button>
|
|
308
303
|
</AccountDropdownFooter>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import useAxios from "axios-hooks";
|
|
2
|
+
import { useEffect } from "react";
|
|
2
3
|
|
|
3
4
|
const useHeader = () => {
|
|
4
5
|
// get user details
|
|
@@ -332,7 +333,6 @@ const useHeader = () => {
|
|
|
332
333
|
}
|
|
333
334
|
};
|
|
334
335
|
|
|
335
|
-
|
|
336
336
|
const [{ ...getTotalMessagesCountData }, getTotalMessagesCount] = useAxios(
|
|
337
337
|
{
|
|
338
338
|
method: "GET",
|
|
@@ -369,6 +369,11 @@ const useHeader = () => {
|
|
|
369
369
|
}
|
|
370
370
|
};
|
|
371
371
|
|
|
372
|
+
//defaults
|
|
373
|
+
// get user info
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
handleGetUserDetails();
|
|
376
|
+
}, []);
|
|
372
377
|
|
|
373
378
|
return {
|
|
374
379
|
handleGetUserDetails,
|
|
@@ -53,6 +53,7 @@ const HeaderComponent = (props) => {
|
|
|
53
53
|
const [languageDropdown, setLanguageDropdown] = useState();
|
|
54
54
|
const [isOpen, setIsOpen] = useState(false);
|
|
55
55
|
// const [searchResultOpen, setSearchResultOpen] = useState(false);
|
|
56
|
+
// in general data find user info and replace where user details is being used
|
|
56
57
|
const {
|
|
57
58
|
handleGetUserAccountsDetail,
|
|
58
59
|
handleGetUserDetails,
|
|
@@ -74,7 +75,7 @@ const HeaderComponent = (props) => {
|
|
|
74
75
|
handleSetDefaultAccount,
|
|
75
76
|
} = useHeader();
|
|
76
77
|
const { pathname } = useLocation();
|
|
77
|
-
const { setGeneralData, generalData, notificationMarkReadData } =
|
|
78
|
+
const { setGeneralData, generalData, notificationMarkReadData, accountName } =
|
|
78
79
|
useContext(OutletContext);
|
|
79
80
|
const [accountType, setAccountType] = useState("");
|
|
80
81
|
const currentAccountType = props?.selectedAccount?.type
|
|
@@ -420,8 +421,7 @@ const HeaderComponent = (props) => {
|
|
|
420
421
|
onClick={() => {
|
|
421
422
|
setLanguageDropdown();
|
|
422
423
|
setIsOpen();
|
|
423
|
-
}}
|
|
424
|
-
>
|
|
424
|
+
}}>
|
|
425
425
|
<li>
|
|
426
426
|
<a className={isHomePage() ? "active" : ""}>
|
|
427
427
|
{getNavLinkIcon()} {props.findText(getNavLinkLabel())}
|
|
@@ -440,8 +440,7 @@ const HeaderComponent = (props) => {
|
|
|
440
440
|
className={
|
|
441
441
|
window.location.pathname.includes("settings") ? "active" : ""
|
|
442
442
|
}
|
|
443
|
-
style={{ cursor: "pointer" }}
|
|
444
|
-
>
|
|
443
|
+
style={{ cursor: "pointer" }}>
|
|
445
444
|
<SettingIcon /> {props.findText("Settings")}
|
|
446
445
|
</a>
|
|
447
446
|
</li>
|
|
@@ -474,8 +473,7 @@ const HeaderComponent = (props) => {
|
|
|
474
473
|
? "disabled"
|
|
475
474
|
: ""
|
|
476
475
|
}
|
|
477
|
-
style={{ cursor: isPendingDelete ? "not-allowed" : "pointer" }}
|
|
478
|
-
>
|
|
476
|
+
style={{ cursor: isPendingDelete ? "not-allowed" : "pointer" }}>
|
|
479
477
|
{unreadNotificationData?.data?.count > 0 ? (
|
|
480
478
|
<NotificationIcon />
|
|
481
479
|
) : (
|
|
@@ -509,8 +507,7 @@ const HeaderComponent = (props) => {
|
|
|
509
507
|
onClick={() => {
|
|
510
508
|
setLanguageDropdown();
|
|
511
509
|
setIsOpen();
|
|
512
|
-
}}
|
|
513
|
-
>
|
|
510
|
+
}}>
|
|
514
511
|
{/* {(!isDeveloper || !window.location.hostname.includes("coming")) && ( // when developer and on staging, don't show
|
|
515
512
|
<SearchInputGroup>
|
|
516
513
|
<SearchIcon />
|
|
@@ -539,8 +536,7 @@ const HeaderComponent = (props) => {
|
|
|
539
536
|
onClick={() => {
|
|
540
537
|
setLanguageDropdown(!languageDropdown);
|
|
541
538
|
setIsOpen();
|
|
542
|
-
}}
|
|
543
|
-
>
|
|
539
|
+
}}>
|
|
544
540
|
<img src={selectedLanguageData[value] || usFlag} alt="" />
|
|
545
541
|
<ArrowDownIcon />
|
|
546
542
|
</div>
|
|
@@ -562,8 +558,7 @@ const HeaderComponent = (props) => {
|
|
|
562
558
|
setIsOpen(!isOpen);
|
|
563
559
|
setLanguageDropdown();
|
|
564
560
|
}}
|
|
565
|
-
ref={secondContainerRef}
|
|
566
|
-
>
|
|
561
|
+
ref={secondContainerRef}>
|
|
567
562
|
<div className="user-img-container">
|
|
568
563
|
<img
|
|
569
564
|
src={props?.selectedAccount?.profile_photo?.url || avatar}
|
|
@@ -571,10 +566,7 @@ const HeaderComponent = (props) => {
|
|
|
571
566
|
/>
|
|
572
567
|
</div>
|
|
573
568
|
<div className="user-info-container">
|
|
574
|
-
<h5 style={{ textTransform: "capitalize" }}>
|
|
575
|
-
{props?.selectedAccount?.metadata?.organization_name ||
|
|
576
|
-
props?.selectedAccount?.display_name}
|
|
577
|
-
</h5>
|
|
569
|
+
<h5 style={{ textTransform: "capitalize" }}>{accountName}</h5>
|
|
578
570
|
<h6 style={{ textTransform: "capitalize" }}>
|
|
579
571
|
{props?.selectedAccount?.type?.toLowerCase()}
|
|
580
572
|
</h6>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
1
|
+
import { useContext, useEffect, useState } from "react";
|
|
2
2
|
import styled from "styled-components";
|
|
3
3
|
// import ExpiryModal from "./components/expiry modal";
|
|
4
4
|
import Search from "../searchBar";
|
|
@@ -9,6 +9,7 @@ import useHeader from "../header/getHeaderDetails";
|
|
|
9
9
|
import OutsideAlerter from "../outsideEventChecker";
|
|
10
10
|
import FullPageLoader from "../fullPageLoader";
|
|
11
11
|
import deleteCookies from "./deleteCookies";
|
|
12
|
+
import { OutletContext } from "..";
|
|
12
13
|
|
|
13
14
|
const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
14
15
|
// const [expiryFlow, setExpiryFlow] = useState();
|
|
@@ -32,12 +33,14 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
32
33
|
}, [generalData?.selectedAccount, search]);
|
|
33
34
|
|
|
34
35
|
const dropdownList = getAllAffiliateData?.data?.results?.map((item) => {
|
|
35
|
-
const image = item?.profile_photo?.url
|
|
36
|
-
return {
|
|
36
|
+
const image = item?.profile_photo?.url;
|
|
37
|
+
return {
|
|
38
|
+
name: item?.metadata.organization_name ?? "...",
|
|
39
|
+
image,
|
|
40
|
+
id: item?.id,
|
|
41
|
+
};
|
|
37
42
|
});
|
|
38
43
|
|
|
39
|
-
console.log(dropdownList, getAllAffiliateData);
|
|
40
|
-
|
|
41
44
|
const [dropdown, setDropdown] = useState(false);
|
|
42
45
|
|
|
43
46
|
const getCookie = () => {
|
|
@@ -111,7 +114,7 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
111
114
|
const value = res?.data;
|
|
112
115
|
const image = value?.profile_photo?.url;
|
|
113
116
|
setSelectedValue(() => ({
|
|
114
|
-
name: value?.
|
|
117
|
+
name: value?.metadata.organization_name,
|
|
115
118
|
image,
|
|
116
119
|
id: value?.id,
|
|
117
120
|
}));
|
|
@@ -130,6 +133,8 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
130
133
|
}
|
|
131
134
|
};
|
|
132
135
|
|
|
136
|
+
const { accountName } = useContext(OutletContext);
|
|
137
|
+
console.log(selectedValue, "selected..");
|
|
133
138
|
return getAllAffiliateData?.data?.results?.length > 0 && !search ? (
|
|
134
139
|
<>
|
|
135
140
|
{(setDefaultAffiliateData?.loading ||
|
|
@@ -160,24 +165,20 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
160
165
|
)} */}
|
|
161
166
|
<Container className="instructor_account_switcher">
|
|
162
167
|
<div className="left">
|
|
163
|
-
{switchValue !== "affiliates" &&
|
|
164
|
-
<p>{generalData?.selectedAccount?.display_name}</p>
|
|
165
|
-
)}
|
|
168
|
+
{switchValue !== "affiliates" && <p>{accountName}</p>}
|
|
166
169
|
{switchValue === "affiliates" && (
|
|
167
170
|
<AffiliatesDropDown>
|
|
168
171
|
{!selectedValue ? (
|
|
169
172
|
<div
|
|
170
173
|
className="placeholder"
|
|
171
|
-
onClick={() => setDropdown(!dropdown)}
|
|
172
|
-
>
|
|
174
|
+
onClick={() => setDropdown(!dropdown)}>
|
|
173
175
|
<p>Select affiliate</p>
|
|
174
176
|
<ArrowDown />
|
|
175
177
|
</div>
|
|
176
178
|
) : (
|
|
177
179
|
<div
|
|
178
180
|
className="selected"
|
|
179
|
-
onClick={() => setDropdown(!dropdown)}
|
|
180
|
-
>
|
|
181
|
+
onClick={() => setDropdown(!dropdown)}>
|
|
181
182
|
<div>
|
|
182
183
|
<img src={selectedValue?.image} alt="" />
|
|
183
184
|
<p>{selectedValue?.name}</p>
|
|
@@ -201,8 +202,7 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
201
202
|
key={item?.id}
|
|
202
203
|
onClick={() => {
|
|
203
204
|
handleSelection(item);
|
|
204
|
-
}}
|
|
205
|
-
>
|
|
205
|
+
}}>
|
|
206
206
|
<div className="left">
|
|
207
207
|
<img src={item?.image} alt="" />
|
|
208
208
|
<p>{item?.name}</p>
|
|
@@ -220,15 +220,13 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
220
220
|
<div className="btn__wrapper">
|
|
221
221
|
<button
|
|
222
222
|
className={switchValue !== "affiliates" ? "active" : ""}
|
|
223
|
-
onClick={() => handleSwitch(1)}
|
|
224
|
-
>
|
|
223
|
+
onClick={() => handleSwitch(1)}>
|
|
225
224
|
<span>My Account</span>
|
|
226
225
|
<span className="circle"></span>
|
|
227
226
|
</button>
|
|
228
227
|
<button
|
|
229
228
|
className={switchValue === "affiliates" ? "active" : ""}
|
|
230
|
-
onClick={() => handleSwitch(2)}
|
|
231
|
-
>
|
|
229
|
+
onClick={() => handleSwitch(2)}>
|
|
232
230
|
<span>Affiliates</span>
|
|
233
231
|
<span className="circle"></span>
|
|
234
232
|
</button>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useState } from "react";
|
|
1
|
+
import React, { useContext, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
3
|
import {
|
|
4
4
|
UserCardContainer,
|
|
@@ -16,7 +16,7 @@ const UserCard = ({ user, isOpen, findText }) => {
|
|
|
16
16
|
const { handleGetUserDetails, userDetails, handleSetDefaultAccount } =
|
|
17
17
|
useHeader();
|
|
18
18
|
const [organizationName, setOrganizationName] = useState();
|
|
19
|
-
const { generalData } = useContext(OutletContext);
|
|
19
|
+
const { generalData, accountName } = useContext(OutletContext);
|
|
20
20
|
|
|
21
21
|
useEffect(() => {
|
|
22
22
|
handleGetUserDetails();
|
|
@@ -59,7 +59,7 @@ const UserCard = ({ user, isOpen, findText }) => {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
}, [generalData]);
|
|
62
|
-
|
|
62
|
+
console.log(generalData, "data...");
|
|
63
63
|
|
|
64
64
|
return (
|
|
65
65
|
<UserCardContainer isOpen={isOpen}>
|
|
@@ -94,20 +94,16 @@ const UserCard = ({ user, isOpen, findText }) => {
|
|
|
94
94
|
if (window.location.pathname.includes("instructor")) {
|
|
95
95
|
window.location.href = `${window.location.protocol}//${window.location.hostname}/instructor/profile`;
|
|
96
96
|
}
|
|
97
|
-
}}
|
|
98
|
-
>
|
|
97
|
+
}}>
|
|
99
98
|
<UserName>{findText("Hello")}</UserName>
|
|
100
99
|
<Handle
|
|
101
100
|
style={{
|
|
102
101
|
textTransform: "capitalize",
|
|
103
102
|
fontWeight: 700,
|
|
104
103
|
fontSize: 15,
|
|
105
|
-
}}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
? organizationName
|
|
109
|
-
: generalData?.selectedAccount?.display_name ?? ""
|
|
110
|
-
}.`}</Handle>
|
|
104
|
+
}}>
|
|
105
|
+
{accountName}
|
|
106
|
+
</Handle>
|
|
111
107
|
</UserDetail>
|
|
112
108
|
</>
|
|
113
109
|
)}
|
|
@@ -326,6 +326,9 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
326
326
|
isProcessingQueue: false,
|
|
327
327
|
});
|
|
328
328
|
|
|
329
|
+
// Ref for synchronous queue processing lock
|
|
330
|
+
const isCurrentlyProcessingRef = useRef(false);
|
|
331
|
+
|
|
329
332
|
// State for rooms that should be automatically marked as read
|
|
330
333
|
const [autoReadRoomIds, setAutoReadRoomIds] = useState(() => new Set());
|
|
331
334
|
|
|
@@ -828,28 +831,45 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
828
831
|
[setState]
|
|
829
832
|
); // Dependency: setState
|
|
830
833
|
|
|
831
|
-
// Effect to process the message queue sequentially
|
|
834
|
+
// Effect to process the message queue sequentially using a Ref lock
|
|
832
835
|
useEffect(() => {
|
|
833
|
-
//
|
|
834
|
-
|
|
836
|
+
// **Synchronous Check + Claim:**
|
|
837
|
+
// Use the ref for an immediate check. If already processing, bail out.
|
|
838
|
+
// Also check if the queue has items.
|
|
839
|
+
if (isCurrentlyProcessingRef.current || state.messageQueue.length === 0) {
|
|
835
840
|
return;
|
|
836
841
|
}
|
|
837
842
|
|
|
838
|
-
//
|
|
843
|
+
// **Claim the processing slot synchronously**
|
|
844
|
+
isCurrentlyProcessingRef.current = true;
|
|
845
|
+
|
|
846
|
+
// Set the state flag as well (useful for potential UI indicators or triggering re-renders upon completion)
|
|
847
|
+
// Note: This state update is still async, but the ref prevents the race condition.
|
|
839
848
|
setState((prevState) => ({ ...prevState, isProcessingQueue: true }));
|
|
840
849
|
|
|
841
|
-
// Get the next message from the front of the queue
|
|
850
|
+
// Get the next message details from the front of the queue.
|
|
851
|
+
// Reading state here is fine, as we've already claimed the slot.
|
|
842
852
|
const messageToSend = state.messageQueue[0];
|
|
853
|
+
|
|
854
|
+
// Safety check: Ensure messageToSend exists (queue might have emptied)
|
|
855
|
+
if (!messageToSend) {
|
|
856
|
+
isCurrentlyProcessingRef.current = false; // Release the claim
|
|
857
|
+
setState((prevState) => ({ ...prevState, isProcessingQueue: false }));
|
|
858
|
+
console.warn(
|
|
859
|
+
"Queue Processor: Attempted to process but queue was empty."
|
|
860
|
+
);
|
|
861
|
+
return; // Nothing to process
|
|
862
|
+
}
|
|
863
|
+
|
|
843
864
|
const { tempId, courseId, accountId, text, media } = messageToSend;
|
|
844
|
-
const pendingKey = `pending_${courseId}_${accountId}`;
|
|
865
|
+
const pendingKey = `pending_${courseId}_${accountId}`;
|
|
845
866
|
|
|
846
|
-
// Define the async task for sending this message
|
|
867
|
+
// Define the async task for sending this specific message.
|
|
847
868
|
const processMessage = async () => {
|
|
848
869
|
try {
|
|
849
870
|
// --- Make API Call ---
|
|
850
871
|
let response;
|
|
851
872
|
// TODO: Handle media preparation (e.g., check if 'media' is File, create FormData)
|
|
852
|
-
// This example assumes 'media' is directly usable or requires minimal processing
|
|
853
873
|
const requestData = { text, media };
|
|
854
874
|
const requestParams = {
|
|
855
875
|
account_id: accountId,
|
|
@@ -857,7 +877,6 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
857
877
|
_account: selectedAccount.id,
|
|
858
878
|
};
|
|
859
879
|
|
|
860
|
-
// (Use the same API call logic as before)
|
|
861
880
|
if (selectedAccount.type.toLowerCase() === "enterprise") {
|
|
862
881
|
response = await request({
|
|
863
882
|
url: "/notify/v1/enterprise/chats/",
|
|
@@ -916,11 +935,23 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
916
935
|
is_delivered: sentMessageData.is_delivered,
|
|
917
936
|
created_at: sentMessageData.created_at,
|
|
918
937
|
user_message: sentMessageData.user_message,
|
|
919
|
-
date: getLocalDateString(sentMessageData.created_at), //
|
|
938
|
+
date: getLocalDateString(sentMessageData.created_at), // Use existing helper
|
|
920
939
|
};
|
|
921
940
|
|
|
922
941
|
// Update state atomically after success
|
|
923
942
|
setState((prevState) => {
|
|
943
|
+
// Safety check: Ensure the message we processed is still the first one
|
|
944
|
+
if (
|
|
945
|
+
prevState.messageQueue.length === 0 ||
|
|
946
|
+
prevState.messageQueue[0].tempId !== tempId
|
|
947
|
+
) {
|
|
948
|
+
console.warn(
|
|
949
|
+
`Queue Processor: Mismatch processing successful message ${tempId}. Queue changed unexpectedly.`
|
|
950
|
+
);
|
|
951
|
+
// Don't modify state if the queue front doesn't match
|
|
952
|
+
return prevState;
|
|
953
|
+
}
|
|
954
|
+
|
|
924
955
|
// 1. Remove optimistic message
|
|
925
956
|
let updatedOptimisticMessages = { ...prevState.optimisticMessages };
|
|
926
957
|
if (updatedOptimisticMessages[pendingKey]) {
|
|
@@ -965,12 +996,11 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
965
996
|
date: messageDateLabel,
|
|
966
997
|
messages: [newChatMessage],
|
|
967
998
|
});
|
|
968
|
-
// Sort groups using helper function
|
|
969
999
|
updatedChatHistory.sort(
|
|
970
1000
|
(a, b) =>
|
|
971
1001
|
getSortableTimestampForGroup(b) -
|
|
972
1002
|
getSortableTimestampForGroup(a)
|
|
973
|
-
);
|
|
1003
|
+
); // Use helper
|
|
974
1004
|
}
|
|
975
1005
|
updatedChats[confirmedRoomId] = updatedChatHistory;
|
|
976
1006
|
|
|
@@ -982,7 +1012,6 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
982
1012
|
const roomIdx = cg.rooms.findIndex((r) => r.id === confirmedRoomId);
|
|
983
1013
|
if (roomIdx !== -1) {
|
|
984
1014
|
roomExists = true;
|
|
985
|
-
// Create a new room object to ensure immutability
|
|
986
1015
|
const updatedRoom = {
|
|
987
1016
|
...cg.rooms[roomIdx],
|
|
988
1017
|
last_message: {
|
|
@@ -992,22 +1021,22 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
992
1021
|
created_at: newChatMessage.created_at,
|
|
993
1022
|
},
|
|
994
1023
|
};
|
|
995
|
-
// Create a new rooms array for the course group
|
|
996
1024
|
const updatedRooms = [...cg.rooms];
|
|
997
1025
|
updatedRooms[roomIdx] = updatedRoom;
|
|
998
|
-
// Return a new course group object
|
|
999
1026
|
return { ...cg, rooms: updatedRooms };
|
|
1000
1027
|
}
|
|
1001
1028
|
return cg;
|
|
1002
1029
|
});
|
|
1003
|
-
// Trigger refetch if room metadata wasn't found (handled outside setState)
|
|
1004
|
-
if (!roomExists) {
|
|
1005
|
-
setTimeout(() => getMessageRoomsByCourses(null, true), 0);
|
|
1006
|
-
}
|
|
1007
1030
|
|
|
1008
1031
|
// 4. Remove processed item from queue (immutable update)
|
|
1009
1032
|
const updatedQueue = prevState.messageQueue.slice(1);
|
|
1010
1033
|
|
|
1034
|
+
// Trigger refetch if room metadata wasn't found (handled outside setState)
|
|
1035
|
+
if (!roomExists && typeof getMessageRoomsByCourses === "function") {
|
|
1036
|
+
// Check if function exists
|
|
1037
|
+
setTimeout(() => getMessageRoomsByCourses(null, true), 0);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1011
1040
|
// Return the combined state updates
|
|
1012
1041
|
return {
|
|
1013
1042
|
...prevState,
|
|
@@ -1015,7 +1044,7 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
1015
1044
|
chats: updatedChats,
|
|
1016
1045
|
roomsByCourses: updatedRoomsByCourses,
|
|
1017
1046
|
messageQueue: updatedQueue,
|
|
1018
|
-
//
|
|
1047
|
+
isProcessingQueue: true, // Keep true until finally block resets it below
|
|
1019
1048
|
};
|
|
1020
1049
|
});
|
|
1021
1050
|
} catch (error) {
|
|
@@ -1025,25 +1054,33 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
1025
1054
|
error
|
|
1026
1055
|
);
|
|
1027
1056
|
setState((prevState) => {
|
|
1057
|
+
// Safety check for queue consistency
|
|
1058
|
+
if (
|
|
1059
|
+
prevState.messageQueue.length === 0 ||
|
|
1060
|
+
prevState.messageQueue[0].tempId !== tempId
|
|
1061
|
+
) {
|
|
1062
|
+
console.warn(
|
|
1063
|
+
`Queue Processor: Mismatch processing failed message ${tempId}. Queue changed unexpectedly.`
|
|
1064
|
+
);
|
|
1065
|
+
return prevState;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1028
1068
|
// 1. Update optimistic message status to 'failed'
|
|
1029
1069
|
let updatedOptimisticMessages = { ...prevState.optimisticMessages };
|
|
1030
1070
|
if (updatedOptimisticMessages[pendingKey]) {
|
|
1031
1071
|
const messageIndex = updatedOptimisticMessages[
|
|
1032
1072
|
pendingKey
|
|
1033
1073
|
].findIndex((msg) => msg.tempId === tempId);
|
|
1034
|
-
// Only update if found and still pending (might have been handled differently elsewhere?)
|
|
1035
1074
|
if (
|
|
1036
1075
|
messageIndex !== -1 &&
|
|
1037
1076
|
updatedOptimisticMessages[pendingKey][messageIndex].status ===
|
|
1038
1077
|
"pending"
|
|
1039
1078
|
) {
|
|
1040
|
-
// Create new message object for immutability
|
|
1041
1079
|
const failedMessage = {
|
|
1042
1080
|
...updatedOptimisticMessages[pendingKey][messageIndex],
|
|
1043
1081
|
status: "failed",
|
|
1044
1082
|
error: error?.message || "Unknown error",
|
|
1045
1083
|
};
|
|
1046
|
-
// Create new array for the pending key
|
|
1047
1084
|
const updatedPendingList = [
|
|
1048
1085
|
...updatedOptimisticMessages[pendingKey],
|
|
1049
1086
|
];
|
|
@@ -1062,33 +1099,39 @@ const useMessageKit = (/*affiliatesActive*/) => {
|
|
|
1062
1099
|
|
|
1063
1100
|
// 2. Remove failed item from queue (immutable update)
|
|
1064
1101
|
const updatedQueue = prevState.messageQueue.slice(1);
|
|
1065
|
-
|
|
1102
|
+
// Return the combined state updates
|
|
1066
1103
|
return {
|
|
1067
1104
|
...prevState,
|
|
1068
1105
|
optimisticMessages: updatedOptimisticMessages,
|
|
1069
1106
|
messageQueue: updatedQueue,
|
|
1070
|
-
//
|
|
1107
|
+
isProcessingQueue: true, // Keep true until finally block resets it below
|
|
1071
1108
|
};
|
|
1072
1109
|
});
|
|
1073
1110
|
} finally {
|
|
1074
1111
|
// --- Finish Processing ---
|
|
1075
|
-
//
|
|
1112
|
+
// **Crucial:** Release the synchronous lock first.
|
|
1113
|
+
isCurrentlyProcessingRef.current = false;
|
|
1114
|
+
// Then update the state flag. This state update will trigger a re-render.
|
|
1115
|
+
// If the queue still has items, the effect will run again, see the ref is false,
|
|
1116
|
+
// claim the lock, and start processing the next item.
|
|
1076
1117
|
setState((prevState) => ({ ...prevState, isProcessingQueue: false }));
|
|
1077
1118
|
}
|
|
1078
1119
|
};
|
|
1079
1120
|
|
|
1080
|
-
// Execute the async sending logic
|
|
1121
|
+
// Execute the async sending logic for the current item.
|
|
1081
1122
|
processMessage();
|
|
1082
1123
|
|
|
1083
|
-
// Dependencies
|
|
1084
|
-
//
|
|
1124
|
+
// Dependencies:
|
|
1125
|
+
// - `state.isProcessingQueue`: Triggers check when processing finishes.
|
|
1126
|
+
// - `state.messageQueue`: Triggers check when queue content changes.
|
|
1127
|
+
// - `request`, `selectedAccount`, `affiliateAccount`, `getMessageRoomsByCourses`:
|
|
1128
|
+
// Required by exhaustive-deps for use within the effect's scope.
|
|
1085
1129
|
}, [
|
|
1086
1130
|
state.isProcessingQueue,
|
|
1087
1131
|
state.messageQueue,
|
|
1088
1132
|
// request,
|
|
1089
1133
|
selectedAccount,
|
|
1090
1134
|
affiliateAccount,
|
|
1091
|
-
// getMessageRoomsByCourses,
|
|
1092
1135
|
]);
|
|
1093
1136
|
|
|
1094
1137
|
/**
|