l-min-components 1.7.1306 → 1.7.1307
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 +7 -5
- package/src/components/AppMainLayout/index.styled.js +1 -1
- package/src/components/InputEmoji/index.jsx +681 -0
- package/src/components/fileRightBar/instructorRightBar2/index.jsx +1 -1
- package/src/components/header/getHeaderDetails.js +13 -9
- package/src/components/header/index.jsx +7 -5
- package/src/components/index.js +1 -0
- package/src/components/instructorAccountSwitcher/index.jsx +38 -29
- package/src/components/sideBar/sideMenu/index.jsx +2 -1
- package/src/components/sideBar/sideMenu/styles/index.jsx +4 -0
- package/src/components/sideBar/userCard/index.jsx +6 -8
- package/src/hooks/leftNavMenu.jsx +4 -0
- package/src/hooks/messaging-kit/index.jsx +44 -61
- package/src/hooks/utils/cookiePolling.jsx +44 -0
package/package.json
CHANGED
|
@@ -131,10 +131,10 @@ const AppMainLayout = ({ children }) => {
|
|
|
131
131
|
handleCurrentSubscription,
|
|
132
132
|
getDefaultAccount,
|
|
133
133
|
handleGetDefaultAccount,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
user,
|
|
135
|
+
userDetails,
|
|
136
|
+
} = useHeader({ default: true });
|
|
137
|
+
// get current default account and store in cookie (from api);
|
|
138
138
|
useEffect(() => {
|
|
139
139
|
if (getDefaultAccount?.data) {
|
|
140
140
|
const date = new Date();
|
|
@@ -298,15 +298,17 @@ const AppMainLayout = ({ children }) => {
|
|
|
298
298
|
setIsTranslationsLoading,
|
|
299
299
|
} = useTranslation(wordBank);
|
|
300
300
|
|
|
301
|
-
const messageKit = useMessageKit(
|
|
301
|
+
const messageKit = useMessageKit(); // useMessageKit
|
|
302
302
|
|
|
303
303
|
return (
|
|
304
304
|
<OutletContext.Provider
|
|
305
305
|
value={{
|
|
306
306
|
messageKit,
|
|
307
307
|
affiliateAccount,
|
|
308
|
+
userDetails,
|
|
308
309
|
findAccountNames,
|
|
309
310
|
accountName,
|
|
311
|
+
user,
|
|
310
312
|
setRightComponent,
|
|
311
313
|
setRightLayout,
|
|
312
314
|
generalData,
|
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useState,
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
forwardRef,
|
|
6
|
+
useImperativeHandle,
|
|
7
|
+
} from "react";
|
|
8
|
+
|
|
9
|
+
// Main React component for the Emoji Input
|
|
10
|
+
// Manages its own state internally (uncontrolled behavior)
|
|
11
|
+
const EmojiInput = forwardRef(
|
|
12
|
+
(
|
|
13
|
+
{
|
|
14
|
+
containerStyle,
|
|
15
|
+
inputStyle,
|
|
16
|
+
buttonStyle,
|
|
17
|
+
pickerStyle,
|
|
18
|
+
onChange, // Exposed onChange prop
|
|
19
|
+
onPaste, // Exposed onPaste prop
|
|
20
|
+
onFocus, // Exposed onFocus prop
|
|
21
|
+
onBlur, // Exposed onBlur prop
|
|
22
|
+
onKeyDown, // Exposed onKeyDown prop
|
|
23
|
+
onKeyUp, // Exposed onKeyUp prop
|
|
24
|
+
onKeyPress, // Exposed onKeyPress prop
|
|
25
|
+
onEnter, // Exposed onEnter prop for handling single Enter key press
|
|
26
|
+
disabled = false, // Disabled prop
|
|
27
|
+
readOnly = false, // ReadOnly prop
|
|
28
|
+
fontSize = "16px", // Font size prop
|
|
29
|
+
lineHeight = "20px", // Line height prop
|
|
30
|
+
// Removed 'value' prop for uncontrolled behavior
|
|
31
|
+
},
|
|
32
|
+
ref
|
|
33
|
+
) => {
|
|
34
|
+
// Internal state is the source of truth for the input value
|
|
35
|
+
const [internalValue, setInternalValue] = useState("");
|
|
36
|
+
|
|
37
|
+
// State to manage the visibility of the emoji picker
|
|
38
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
39
|
+
// State to manage the position of the emoji picker ('top' or 'bottom')
|
|
40
|
+
const [pickerPosition, setPickerPosition] = useState("bottom"); // Default to bottom (above input)
|
|
41
|
+
// State to manage the emoji search term
|
|
42
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
43
|
+
|
|
44
|
+
// Ref to access the textarea element directly
|
|
45
|
+
const textareaRef = useRef(null);
|
|
46
|
+
// Ref to access the input container element
|
|
47
|
+
const containerRef = useRef(null);
|
|
48
|
+
// Ref to access the emoji picker element
|
|
49
|
+
const emojiPickerRef = useRef(null);
|
|
50
|
+
// Ref to access the emoji toggle button element
|
|
51
|
+
const emojiToggleRef = useRef(null);
|
|
52
|
+
|
|
53
|
+
// Expose imperative methods to the parent component via the ref
|
|
54
|
+
useImperativeHandle(ref, () => ({
|
|
55
|
+
// Method to get the current input value (from internal state)
|
|
56
|
+
getValue: () => internalValue,
|
|
57
|
+
// Method to set the input value (updates internal state and calls onChange)
|
|
58
|
+
setValue: (newValue) => {
|
|
59
|
+
setInternalValue(newValue);
|
|
60
|
+
// Manually trigger onChange with the new value
|
|
61
|
+
if (onChange) {
|
|
62
|
+
const event = { target: { value: newValue } };
|
|
63
|
+
onChange(event);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
// Method to focus the textarea
|
|
67
|
+
focus: () => {
|
|
68
|
+
if (textareaRef.current) {
|
|
69
|
+
textareaRef.current.focus();
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
// Method to clear the input (updates internal state to empty and calls onChange)
|
|
73
|
+
clear: () => {
|
|
74
|
+
setInternalValue("");
|
|
75
|
+
// Manually trigger onChange with empty string
|
|
76
|
+
if (onChange) {
|
|
77
|
+
const event = { target: { value: "" } };
|
|
78
|
+
onChange(event);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
// Method to toggle the emoji picker visibility
|
|
82
|
+
toggleEmojiPicker: () => togglePicker(),
|
|
83
|
+
// Method to show the emoji picker
|
|
84
|
+
showEmojiPicker: () => setShowPicker(true),
|
|
85
|
+
// Method to hide the emoji picker
|
|
86
|
+
hideEmojiPicker: () => setShowPicker(false),
|
|
87
|
+
// You can expose other methods as needed
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
// Expanded list of emojis with names/keywords for search
|
|
91
|
+
const allEmojisWithNames = [
|
|
92
|
+
{ emoji: "😊", name: "Smiling Face with Smiling Eyes" },
|
|
93
|
+
{ emoji: "😂", name: "Face with Tears of Joy" },
|
|
94
|
+
{ emoji: "😍", name: "Smiling Face with Heart-Eyes" },
|
|
95
|
+
{ emoji: "🥰", name: "Smiling Face with Hearts" },
|
|
96
|
+
{ emoji: "😘", name: "Face Blowing a Kiss" },
|
|
97
|
+
{ emoji: "😗", name: "Kissing Face" },
|
|
98
|
+
{ emoji: "😙", name: "Kissing Face with Smiling Eyes" },
|
|
99
|
+
{ emoji: "😚", name: "Kissing Face with Closed Eyes" },
|
|
100
|
+
{ emoji: "😋", name: "Face Savoring Food" },
|
|
101
|
+
{ emoji: "😛", name: "Face with Tongue" },
|
|
102
|
+
{ emoji: "😜", name: "Winking Face with Tongue" },
|
|
103
|
+
{ emoji: "🤪", name: "Zany Face" },
|
|
104
|
+
{ emoji: "🤨", name: "Face with Raised Eyebrow" },
|
|
105
|
+
{ emoji: "🧐", name: "Face with Monocle" },
|
|
106
|
+
{ emoji: "🤓", name: "Nerd Face" },
|
|
107
|
+
{ emoji: "😎", name: "Smiling Face with Sunglasses" },
|
|
108
|
+
{ emoji: "🤩", name: "Star-Struck" },
|
|
109
|
+
{ emoji: "🥳", name: "Partying Face" },
|
|
110
|
+
{ emoji: "😏", name: "Smirking Face" },
|
|
111
|
+
{ emoji: "😒", name: "Unamused Face" },
|
|
112
|
+
{ emoji: "😞", name: "Disappointed Face" },
|
|
113
|
+
{ emoji: "😔", name: "Pensive Face" },
|
|
114
|
+
{ emoji: "😟", name: "Worried Face" },
|
|
115
|
+
{ emoji: "😕", name: "Confused Face" },
|
|
116
|
+
{ emoji: "🙁", name: "Slightly Frowning Face" },
|
|
117
|
+
{ emoji: "☹️", name: "Frowning Face" },
|
|
118
|
+
{ emoji: "😣", name: "Persevering Face" },
|
|
119
|
+
{ emoji: "😖", name: "Confounded Face" },
|
|
120
|
+
{ emoji: "😫", name: "Tired Face" },
|
|
121
|
+
{ emoji: "😩", name: "Weary Face" },
|
|
122
|
+
{ emoji: "🥺", name: "Pleading Face" },
|
|
123
|
+
{ emoji: "😢", name: "Crying Face" },
|
|
124
|
+
{ emoji: "😭", name: "Loudly Crying Face" },
|
|
125
|
+
{ emoji: "😤", name: "Face with Steam From Nose" },
|
|
126
|
+
{ emoji: "😠", name: "Angry Face" },
|
|
127
|
+
{ emoji: "😡", name: "Pouting Face" },
|
|
128
|
+
{ emoji: "🤬", name: "Face with Symbols on Mouth" },
|
|
129
|
+
{ emoji: "😈", name: "Smiling Face with Horns" },
|
|
130
|
+
{ emoji: "👿", name: "Angry Face with Horns" },
|
|
131
|
+
{ emoji: "💀", name: "Skull" },
|
|
132
|
+
{ emoji: "👻", name: "Ghost" },
|
|
133
|
+
{ emoji: "👽", name: "Alien" },
|
|
134
|
+
{ emoji: "🤖", name: "Robot" },
|
|
135
|
+
{ emoji: "🎃", name: "Jack-O-Lantern" },
|
|
136
|
+
{ emoji: "😺", name: "Grinning Cat" },
|
|
137
|
+
{ emoji: "😸", name: "Grinning Cat with Smiling Eyes" },
|
|
138
|
+
{ emoji: "😹", name: "Cat with Tears of Joy" },
|
|
139
|
+
{ emoji: "😻", name: "Smiling Cat with Heart-Eyes" },
|
|
140
|
+
{ emoji: "😼", name: "Cat with Wry Smile" },
|
|
141
|
+
{ emoji: "😽", name: "Kissing Cat" },
|
|
142
|
+
{ emoji: "🙀", name: "Weary Cat" },
|
|
143
|
+
{ emoji: "😿", name: "Crying Cat" },
|
|
144
|
+
{ emoji: "😾", name: "Pouting Cat" },
|
|
145
|
+
{ emoji: "👍", name: "Thumbs Up" },
|
|
146
|
+
{ emoji: "👎", name: "Thumbs Down" },
|
|
147
|
+
{ emoji: "👏", name: "Clapping Hands" },
|
|
148
|
+
{ emoji: "🙌", name: "Raising Hands" },
|
|
149
|
+
{ emoji: "🤲", name: "Palms Up Together" },
|
|
150
|
+
{ emoji: "🤝", name: "Handshake" },
|
|
151
|
+
{ emoji: "🙏", name: "Folded Hands" },
|
|
152
|
+
{ emoji: "💪", name: "Flexed Biceps" },
|
|
153
|
+
{ emoji: "👈", name: "Backhand Index Pointing Left" },
|
|
154
|
+
{ emoji: "👉", name: "Backhand Index Pointing Right" },
|
|
155
|
+
{ emoji: "☝️", name: "Index Pointing Up" },
|
|
156
|
+
{ emoji: "👆", name: "Index Pointing Up" }, // Duplicate, can be refined
|
|
157
|
+
{ emoji: "👇", name: "Index Pointing Down" },
|
|
158
|
+
{ emoji: "✌️", name: "Victory Hand" },
|
|
159
|
+
{ emoji: "🤞", name: "Crossed Fingers" },
|
|
160
|
+
{ emoji: "🖖", name: "Vulcan Salute" },
|
|
161
|
+
{ emoji: "🤘", name: "Sign of the Horns" },
|
|
162
|
+
{ emoji: "🤙", name: "Call Me Hand" },
|
|
163
|
+
{ emoji: "🖐️", name: "Hand with Fingers Splayed" },
|
|
164
|
+
{ emoji: "✋", name: "Raised Hand" },
|
|
165
|
+
{ emoji: "👌", name: "OK Hand" },
|
|
166
|
+
{ emoji: "🤏", name: "Pinched Fingers" },
|
|
167
|
+
{ emoji: "👋", name: "Waving Hand" },
|
|
168
|
+
{ emoji: "🤚", name: "Raised Back of Hand" },
|
|
169
|
+
{ emoji: "❤️", name: "Red Heart" },
|
|
170
|
+
{ emoji: "🧡", name: "Orange Heart" },
|
|
171
|
+
{ emoji: "💛", name: "Yellow Heart" },
|
|
172
|
+
{ emoji: "💚", name: "Green Heart" },
|
|
173
|
+
{ emoji: "💙", name: "Blue Heart" },
|
|
174
|
+
{ emoji: "💜", name: "Purple Heart" },
|
|
175
|
+
{ emoji: "🖤", name: "Black Heart" },
|
|
176
|
+
{ emoji: "🤍", name: "White Heart" },
|
|
177
|
+
{ emoji: "🤎", name: "Brown Heart" },
|
|
178
|
+
{ emoji: "💔", name: "Broken Heart" },
|
|
179
|
+
{ emoji: "❣️", name: "Heart Exclamation" },
|
|
180
|
+
{ emoji: "💕", name: "Two Hearts" },
|
|
181
|
+
{ emoji: "💞", name: "Revolving Hearts" },
|
|
182
|
+
{ emoji: "💓", name: "Beating Heart" },
|
|
183
|
+
{ emoji: "💗", name: "Growing Heart" },
|
|
184
|
+
{ emoji: "💖", name: "Sparkling Heart" },
|
|
185
|
+
{ emoji: "💘", name: "Heart with Arrow" },
|
|
186
|
+
{ emoji: "💝", name: "Heart with Ribbon" },
|
|
187
|
+
{ emoji: "💯", name: "Hundred Points" },
|
|
188
|
+
{ emoji: "💢", name: "Anger Symbol" },
|
|
189
|
+
{ emoji: "💥", name: "Collision" },
|
|
190
|
+
{ emoji: "💫", name: "Dizzy" },
|
|
191
|
+
{ emoji: "💦", name: "Sweat Droplets" },
|
|
192
|
+
{ emoji: "💨", name: "Dashing Away" },
|
|
193
|
+
{ emoji: "💩", name: "Pile of Poo" },
|
|
194
|
+
{ emoji: "🍎", name: "Red Apple" },
|
|
195
|
+
{ emoji: "🍌", name: "Banana" },
|
|
196
|
+
{ emoji: "🍒", name: "Cherries" },
|
|
197
|
+
{ emoji: "🍓", name: "Strawberry" },
|
|
198
|
+
{ emoji: "🍔", name: "Hamburger" },
|
|
199
|
+
{ emoji: "🍕", name: "Pizza" },
|
|
200
|
+
{ emoji: "🍟", name: "French Fries" },
|
|
201
|
+
{ emoji: "🍦", name: "Soft Ice Cream" },
|
|
202
|
+
{ emoji: "🍩", name: "Doughnut" },
|
|
203
|
+
{ emoji: "☕", name: "Hot Beverage" },
|
|
204
|
+
{ emoji: "🍵", name: "Teacup Without Handle" },
|
|
205
|
+
{ emoji: "🍺", name: "Beer Bottle" },
|
|
206
|
+
{ emoji: "🍷", name: "Wine Glass" },
|
|
207
|
+
{ emoji: "🍸", name: "Cocktail Glass" },
|
|
208
|
+
{ emoji: "🍹", name: "Tropical Drink" },
|
|
209
|
+
{ emoji: "🍾", name: "Bottle with Popping Cork" },
|
|
210
|
+
{ emoji: "🌍", name: "Earth Globe Europe-Africa" },
|
|
211
|
+
{ emoji: "🌎", name: "Earth Globe Americas" },
|
|
212
|
+
{ emoji: "🌏", name: "Earth Globe Asia-Australia" },
|
|
213
|
+
{ emoji: "🌞", name: "Sun with Face" },
|
|
214
|
+
{ emoji: "🌝", name: "Full Moon Face" },
|
|
215
|
+
{ emoji: "🌚", name: "New Moon Face" },
|
|
216
|
+
{ emoji: "🌙", name: "Crescent Moon" },
|
|
217
|
+
{ emoji: "⭐", name: "Star" },
|
|
218
|
+
{ emoji: "🌟", name: "Glowing Star" },
|
|
219
|
+
{ emoji: "✨", name: "Sparkles" },
|
|
220
|
+
{ emoji: "⚡", name: "High Voltage" },
|
|
221
|
+
{ emoji: "🔥", name: "Fire" },
|
|
222
|
+
{ emoji: "💧", name: "Droplet" },
|
|
223
|
+
{ emoji: "🌊", name: "Water Wave" },
|
|
224
|
+
{ emoji: "🌈", name: "Rainbow" },
|
|
225
|
+
{ emoji: "☁️", name: "Cloud" },
|
|
226
|
+
{ emoji: "🐶", name: "Dog Face" },
|
|
227
|
+
{ emoji: "🐱", name: "Cat Face" },
|
|
228
|
+
{ emoji: "🐭", name: "Mouse Face" },
|
|
229
|
+
{ emoji: "🐹", name: "Hamster Face" },
|
|
230
|
+
{ emoji: "🐰", name: "Rabbit Face" },
|
|
231
|
+
{ emoji: "🦊", name: "Fox Face" },
|
|
232
|
+
{ emoji: "🐻", name: "Bear" },
|
|
233
|
+
{ emoji: "🐼", name: "Panda" },
|
|
234
|
+
{ emoji: "🐨", name: "Koala" },
|
|
235
|
+
{ emoji: "🐯", name: "Tiger Face" },
|
|
236
|
+
{ emoji: "🦁", name: "Lion" },
|
|
237
|
+
{ emoji: "🐮", name: "Cow Face" },
|
|
238
|
+
{ emoji: "🐷", name: "Pig Face" },
|
|
239
|
+
{ emoji: "🐸", name: "Frog" },
|
|
240
|
+
{ emoji: "🐵", name: "Monkey Face" },
|
|
241
|
+
{ emoji: "🐔", name: "Chicken" },
|
|
242
|
+
{ emoji: "⚽", name: "Soccer Ball" },
|
|
243
|
+
{ emoji: "🏀", name: "Basketball" },
|
|
244
|
+
{ emoji: "🏈", name: "American Football" },
|
|
245
|
+
{ emoji: "⚾", name: "Baseball" },
|
|
246
|
+
{ emoji: "🎾", name: "Tennis Racquet and Ball" },
|
|
247
|
+
{ emoji: "🏐", name: "Volleyball" },
|
|
248
|
+
{ emoji: "🏉", name: "Rugby Football" },
|
|
249
|
+
{ emoji: "🎱", name: "Pool 8 Ball" },
|
|
250
|
+
{ emoji: "🏓", name: "Table Tennis Paddle and Ball" },
|
|
251
|
+
{ emoji: "🏸", name: "Badminton Racquet and Shuttlecock" },
|
|
252
|
+
{ emoji: "🏒", name: "Ice Hockey Stick and Puck" },
|
|
253
|
+
{ emoji: "🥅", name: "Goal Net" },
|
|
254
|
+
{ emoji: "⛳", name: "Flag in Hole" },
|
|
255
|
+
{ emoji: "🏹", name: "Bow and Arrow" },
|
|
256
|
+
{ emoji: "🎣", name: "Fishing Pole" },
|
|
257
|
+
{ emoji: "🥊", name: "Boxing Glove" },
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
// Filter emojis based on the search term (case-insensitive)
|
|
261
|
+
const filteredEmojis = allEmojisWithNames.filter((item) => {
|
|
262
|
+
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
|
263
|
+
// Check if the search term is included in the emoji's name
|
|
264
|
+
return (
|
|
265
|
+
searchTerm === "" ||
|
|
266
|
+
item.name.toLowerCase().includes(lowerCaseSearchTerm)
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Function to insert an emoji at the current cursor position
|
|
271
|
+
const insertEmoji = (emoji) => {
|
|
272
|
+
const textarea = textareaRef.current;
|
|
273
|
+
if (!textarea) return; // Ensure textarea is available
|
|
274
|
+
|
|
275
|
+
const cursorPosition = textarea.selectionStart;
|
|
276
|
+
const textBefore = internalValue.substring(0, cursorPosition);
|
|
277
|
+
const textAfter = internalValue.substring(cursorPosition);
|
|
278
|
+
|
|
279
|
+
// Calculate the new value
|
|
280
|
+
const newValue = textBefore + emoji + textAfter;
|
|
281
|
+
|
|
282
|
+
// Update internal state
|
|
283
|
+
setInternalValue(newValue);
|
|
284
|
+
|
|
285
|
+
// Manually trigger onChange with the new value
|
|
286
|
+
if (onChange) {
|
|
287
|
+
const event = { target: { value: newValue } };
|
|
288
|
+
onChange(event);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Move cursor to after the inserted emoji
|
|
292
|
+
const newCursorPosition = cursorPosition + emoji.length;
|
|
293
|
+
|
|
294
|
+
// Use a timeout to ensure the state updates before setting selection
|
|
295
|
+
// This is a common pattern in React for DOM manipulations after state updates
|
|
296
|
+
setTimeout(() => {
|
|
297
|
+
textarea.selectionStart = newCursorPosition;
|
|
298
|
+
textarea.selectionEnd = newCursorPosition;
|
|
299
|
+
textarea.focus(); // Focus the textarea after inserting
|
|
300
|
+
}, 0);
|
|
301
|
+
|
|
302
|
+
// Optionally hide the picker after selection
|
|
303
|
+
// setShowPicker(false);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Effect to handle clicks outside the emoji picker to close it
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
const handleClickOutside = (event) => {
|
|
309
|
+
// Check if the click was outside the picker and not on the toggle button or the picker itself
|
|
310
|
+
if (
|
|
311
|
+
emojiPickerRef.current &&
|
|
312
|
+
!emojiPickerRef.current.contains(event.target) &&
|
|
313
|
+
event.target !== emojiToggleRef.current &&
|
|
314
|
+
!emojiToggleRef.current.contains(event.target)
|
|
315
|
+
) {
|
|
316
|
+
setShowPicker(false);
|
|
317
|
+
setSearchTerm(""); // Clear search term when closing
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Add the event listener
|
|
322
|
+
document.addEventListener("click", handleClickOutside);
|
|
323
|
+
|
|
324
|
+
// Cleanup the event listener on component unmount
|
|
325
|
+
return () => {
|
|
326
|
+
document.removeEventListener("click", handleClickOutside);
|
|
327
|
+
};
|
|
328
|
+
}, [showPicker]); // Re-run effect if showPicker changes
|
|
329
|
+
|
|
330
|
+
// Function to determine picker position based on available space
|
|
331
|
+
const determinePickerPosition = () => {
|
|
332
|
+
const container = containerRef.current;
|
|
333
|
+
if (!container) return "bottom"; // Default to bottom if container not found
|
|
334
|
+
|
|
335
|
+
const rect = container.getBoundingClientRect();
|
|
336
|
+
const viewportHeight = window.innerHeight;
|
|
337
|
+
|
|
338
|
+
// Calculate space above and below the input container
|
|
339
|
+
const spaceAbove = rect.top;
|
|
340
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
341
|
+
|
|
342
|
+
// Assume picker needs at least 200px height (based on max-height)
|
|
343
|
+
const requiredSpace = 200; // Match max-height of picker
|
|
344
|
+
|
|
345
|
+
// If there's enough space below, position below. Otherwise, position above.
|
|
346
|
+
// Prioritize below if space is sufficient, otherwise check above.
|
|
347
|
+
if (spaceBelow >= requiredSpace) {
|
|
348
|
+
return "top"; // Position below the input (top: 100%)
|
|
349
|
+
} else if (spaceAbove >= requiredSpace) {
|
|
350
|
+
return "bottom"; // Position above the input (bottom: 100%)
|
|
351
|
+
} else {
|
|
352
|
+
// If neither has enough space, position where there is more space
|
|
353
|
+
return spaceBelow > spaceAbove ? "top" : "bottom";
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// Toggle emoji picker visibility and determine position
|
|
358
|
+
const togglePicker = () => {
|
|
359
|
+
if (!showPicker) {
|
|
360
|
+
// If about to show, determine the best position
|
|
361
|
+
setPickerPosition(determinePickerPosition());
|
|
362
|
+
} else {
|
|
363
|
+
setSearchTerm(""); // Clear search term when hiding
|
|
364
|
+
}
|
|
365
|
+
setShowPicker(!showPicker);
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
// Apply containerStyle prop
|
|
370
|
+
<div
|
|
371
|
+
className="input-container"
|
|
372
|
+
style={containerStyle}
|
|
373
|
+
ref={containerRef}>
|
|
374
|
+
{/* Textarea for input */}
|
|
375
|
+
{/* Apply inputStyle prop */}
|
|
376
|
+
<textarea
|
|
377
|
+
ref={textareaRef} // Attach ref to textarea
|
|
378
|
+
className="text-input"
|
|
379
|
+
rows="1" // Start with 1 row, max-height will control size
|
|
380
|
+
placeholder="Start typing..." // Updated placeholder text
|
|
381
|
+
// Use internal state as the source of truth
|
|
382
|
+
value={internalValue}
|
|
383
|
+
onChange={(e) => {
|
|
384
|
+
// Update internal state
|
|
385
|
+
setInternalValue(e.target.value);
|
|
386
|
+
// Always call the passed-in onChange handler if it exists
|
|
387
|
+
if (onChange) {
|
|
388
|
+
onChange(e);
|
|
389
|
+
}
|
|
390
|
+
}}
|
|
391
|
+
onPaste={(e) => {
|
|
392
|
+
if (onPaste) {
|
|
393
|
+
onPaste(e); // Call the passed-in onPaste handler
|
|
394
|
+
}
|
|
395
|
+
// onChange handler will be triggered by paste, updating internal state
|
|
396
|
+
}}
|
|
397
|
+
onFocus={(e) => {
|
|
398
|
+
// Added onFocus handler
|
|
399
|
+
if (onFocus) {
|
|
400
|
+
onFocus(e);
|
|
401
|
+
}
|
|
402
|
+
}}
|
|
403
|
+
onBlur={(e) => {
|
|
404
|
+
// Added onBlur handler
|
|
405
|
+
if (onBlur) {
|
|
406
|
+
onBlur(e);
|
|
407
|
+
}
|
|
408
|
+
}}
|
|
409
|
+
onKeyDown={(e) => {
|
|
410
|
+
// Added onKeyDown handler
|
|
411
|
+
// Check for Enter key press
|
|
412
|
+
if (e.key === "Enter") {
|
|
413
|
+
// If Shift is NOT pressed, prevent default and call onEnter
|
|
414
|
+
if (!e.shiftKey) {
|
|
415
|
+
e.preventDefault();
|
|
416
|
+
if (onEnter) {
|
|
417
|
+
onEnter(internalValue); // Pass current internal value to onEnter
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// If Shift IS pressed, allow default (new line)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Always call the passed-in onKeyDown handler if it exists
|
|
424
|
+
if (onKeyDown) {
|
|
425
|
+
onKeyDown(e);
|
|
426
|
+
}
|
|
427
|
+
}}
|
|
428
|
+
onKeyUp={(e) => {
|
|
429
|
+
// Added onKeyUp handler
|
|
430
|
+
if (onKeyUp) {
|
|
431
|
+
onKeyUp(e);
|
|
432
|
+
}
|
|
433
|
+
}}
|
|
434
|
+
onKeyPress={(e) => {
|
|
435
|
+
// Added onKeyPress handler
|
|
436
|
+
if (onKeyPress) {
|
|
437
|
+
onKeyPress(e);
|
|
438
|
+
}
|
|
439
|
+
}}
|
|
440
|
+
disabled={disabled} // Apply disabled prop
|
|
441
|
+
readOnly={readOnly} // Apply readOnly prop
|
|
442
|
+
style={{
|
|
443
|
+
maxHeight: "52px",
|
|
444
|
+
overflowY: "auto",
|
|
445
|
+
fontSize: fontSize, // Apply fontSize prop
|
|
446
|
+
lineHeight: lineHeight, // Apply lineHeight prop
|
|
447
|
+
...inputStyle, // Apply custom inputStyle prop
|
|
448
|
+
}} // Set max height and overflow, apply inputStyle
|
|
449
|
+
></textarea>
|
|
450
|
+
|
|
451
|
+
{/* Button to toggle the emoji picker */}
|
|
452
|
+
{/* Apply buttonStyle prop */}
|
|
453
|
+
<button
|
|
454
|
+
ref={emojiToggleRef} // Attach ref to toggle button
|
|
455
|
+
className="emoji-toggle-button"
|
|
456
|
+
onClick={togglePicker} // Use the new togglePicker function
|
|
457
|
+
// Disable button if input is disabled or readOnly
|
|
458
|
+
disabled={disabled || readOnly}
|
|
459
|
+
style={buttonStyle} // Apply buttonStyle
|
|
460
|
+
>
|
|
461
|
+
{/* SVG Icon */}
|
|
462
|
+
<svg
|
|
463
|
+
width="24"
|
|
464
|
+
height="25"
|
|
465
|
+
viewBox="0 0 24 25"
|
|
466
|
+
fill="none"
|
|
467
|
+
xmlns="http://www.w3.org/2000/svg">
|
|
468
|
+
<path
|
|
469
|
+
d="M19.0668 5.51844C23.0368 9.48844 22.9668 15.9684 18.8668 19.8584C15.0768 23.4484 8.92679 23.4484 5.12679 19.8584C1.01679 15.9684 0.946776 9.48844 4.92678 5.51844C8.82678 1.60844 15.1668 1.60844 19.0668 5.51844Z"
|
|
470
|
+
stroke="currentColor"
|
|
471
|
+
strokeWidth="1.5"
|
|
472
|
+
strokeLinecap="round"
|
|
473
|
+
strokeLinejoin="round"
|
|
474
|
+
/>
|
|
475
|
+
<path
|
|
476
|
+
d="M15.834 16.6367C13.714 18.6367 10.2741 18.6367 8.16406 16.6367"
|
|
477
|
+
stroke="currentColor"
|
|
478
|
+
strokeWidth="1.5"
|
|
479
|
+
strokeLinecap="round"
|
|
480
|
+
strokeLinejoin="round"
|
|
481
|
+
/>
|
|
482
|
+
<path
|
|
483
|
+
d="M8.66638 9.55859H8.67536"
|
|
484
|
+
stroke="currentColor"
|
|
485
|
+
strokeWidth="2.5"
|
|
486
|
+
strokeLinecap="round"
|
|
487
|
+
strokeLinejoin="round"
|
|
488
|
+
/>
|
|
489
|
+
<path
|
|
490
|
+
d="M15.8383 9.55859H15.8472"
|
|
491
|
+
stroke="currentColor"
|
|
492
|
+
strokeWidth="2.5"
|
|
493
|
+
strokeLinecap="round"
|
|
494
|
+
strokeLinejoin="round"
|
|
495
|
+
/>
|
|
496
|
+
</svg>
|
|
497
|
+
</button>
|
|
498
|
+
|
|
499
|
+
{/* Emoji picker container, shown conditionally */}
|
|
500
|
+
{showPicker && (
|
|
501
|
+
// Apply pickerStyle prop and dynamic position class
|
|
502
|
+
<div
|
|
503
|
+
ref={emojiPickerRef} // Attach ref to picker container
|
|
504
|
+
className={`emoji-picker-container show ${pickerPosition}`} // Add pickerPosition class
|
|
505
|
+
style={pickerStyle} // Apply pickerStyle
|
|
506
|
+
>
|
|
507
|
+
{/* Emoji Search Input */}
|
|
508
|
+
<input
|
|
509
|
+
type="text"
|
|
510
|
+
placeholder="Search emojis..."
|
|
511
|
+
className="emoji-search-input"
|
|
512
|
+
value={searchTerm}
|
|
513
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
514
|
+
/>
|
|
515
|
+
|
|
516
|
+
{/* Emoji Grid */}
|
|
517
|
+
<div className="emoji-grid">
|
|
518
|
+
{/* Map over filtered emojis and create buttons */}
|
|
519
|
+
{filteredEmojis.map((item, index) => (
|
|
520
|
+
<button
|
|
521
|
+
key={index} // Use index as key (safe for static list)
|
|
522
|
+
className="emoji-button"
|
|
523
|
+
onClick={() => insertEmoji(item.emoji)} // Insert emoji on click
|
|
524
|
+
title={item.name} // Add title for accessibility
|
|
525
|
+
>
|
|
526
|
+
{item.emoji}
|
|
527
|
+
</button>
|
|
528
|
+
))}
|
|
529
|
+
{filteredEmojis.length === 0 && (
|
|
530
|
+
<p className="no-results">No emojis found.</p>
|
|
531
|
+
)}
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
)}
|
|
535
|
+
{/* Plain CSS styles */}
|
|
536
|
+
<style>{`
|
|
537
|
+
.input-container {
|
|
538
|
+
position: relative;
|
|
539
|
+
width: 100%;
|
|
540
|
+
max-width: 400px; /* Adjusted max-width slightly for a common input size */
|
|
541
|
+
margin: 20px auto;
|
|
542
|
+
/* Updated default border color */
|
|
543
|
+
border: 1px solid rgba(229, 229, 234, 1);
|
|
544
|
+
border-radius: 0.375rem; /* Default rounded-md */
|
|
545
|
+
/* Removed overflow: hidden; */
|
|
546
|
+
/* Default background, can be overridden by prop */
|
|
547
|
+
background-color: #ffffff;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.text-input {
|
|
551
|
+
display: block; /* Make it a block element */
|
|
552
|
+
width: 100%;
|
|
553
|
+
padding: 8px; /* p-2 */
|
|
554
|
+
padding-right: 40px; /* Adjust padding to make space for the button */
|
|
555
|
+
border: none; /* Removed border */
|
|
556
|
+
outline: none; /* Removed default outline */
|
|
557
|
+
resize: none; /* Disable resize handle */
|
|
558
|
+
max-height: 52px; /* Set the maximum height */
|
|
559
|
+
overflow-y: auto; /* Add scrollbar if content exceeds max-height */
|
|
560
|
+
font-family: sans-serif; /* Use a common sans-serif font */
|
|
561
|
+
box-sizing: border-box; /* Include padding and border in element's total width and height */
|
|
562
|
+
/* Default background, can be overridden by prop */
|
|
563
|
+
background-color: transparent; /* Make textarea background transparent to show container background */
|
|
564
|
+
color: #374151; /* Set text color to a shade of gray */
|
|
565
|
+
/* Removed hardcoded font-size and line-height here */
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.text-input:disabled,
|
|
569
|
+
.text-input[readOnly] {
|
|
570
|
+
background-color: #f3f4f6; /* gray-100 */
|
|
571
|
+
cursor: not-allowed;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
.emoji-toggle-button {
|
|
576
|
+
position: absolute;
|
|
577
|
+
top: 50%; /* Center vertically */
|
|
578
|
+
right: 8px; /* Position from the right */
|
|
579
|
+
transform: translateY(-50%); /* Adjust for vertical centering */
|
|
580
|
+
display: inline-flex;
|
|
581
|
+
align-items: center;
|
|
582
|
+
justify-content: center;
|
|
583
|
+
padding: 4px; /* p-1 */
|
|
584
|
+
color: #6b7280; /* gray-500 */
|
|
585
|
+
cursor: pointer;
|
|
586
|
+
background: none; /* No background */
|
|
587
|
+
border: none; /* No border */
|
|
588
|
+
transition: color 0.2s ease-in-out;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.emoji-toggle-button:hover:not(:disabled) {
|
|
592
|
+
color: #4b5563; /* gray-700 */
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.emoji-toggle-button:disabled {
|
|
596
|
+
cursor: not-allowed;
|
|
597
|
+
opacity: 0.6;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
.emoji-toggle-button svg {
|
|
602
|
+
stroke: currentColor; /* Make SVG stroke color match text color */
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.emoji-picker-container {
|
|
606
|
+
display: none; /* Hidden by default */
|
|
607
|
+
position: absolute;
|
|
608
|
+
left: 0;
|
|
609
|
+
right: 0;
|
|
610
|
+
z-index: 10;
|
|
611
|
+
max-height: 192px; /* max-h-48 */
|
|
612
|
+
overflow-y: hidden; /* Hide overflow for the container itself */
|
|
613
|
+
background-color: #ffffff; /* bg-white */
|
|
614
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* shadow-md */
|
|
615
|
+
border: 1px solid #e5e7eb; /* gray-200 */
|
|
616
|
+
border-radius: 0.375rem; /* rounded-md */
|
|
617
|
+
padding: 8px; /* p-2 */
|
|
618
|
+
flex-direction: column; /* Stack search and grid vertically */
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.emoji-picker-container.show {
|
|
622
|
+
display: flex; /* Show as flex container when 'show' class is present */
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.emoji-search-input {
|
|
626
|
+
width: 100%;
|
|
627
|
+
padding: 8px;
|
|
628
|
+
margin-bottom: 8px; /* Space below search input */
|
|
629
|
+
border: 1px solid #d1d5db;
|
|
630
|
+
border-radius: 0.25rem;
|
|
631
|
+
outline: none;
|
|
632
|
+
box-sizing: border-box;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.emoji-grid {
|
|
636
|
+
display: grid; /* Use grid for layout */
|
|
637
|
+
grid-template-columns: repeat(auto-fill, minmax(30px, 1fr)); /* Responsive columns */
|
|
638
|
+
gap: 4px; /* gap-1 */
|
|
639
|
+
overflow-y: auto; /* Add scrollbar to the grid */
|
|
640
|
+
max-height: calc(100% - 40px); /* Calculate remaining height for grid (adjust 40px based on search input height + margin) */
|
|
641
|
+
padding-right: 8px; /* Add padding for scrollbar */
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
/* Positioning classes */
|
|
646
|
+
.emoji-picker-container.bottom {
|
|
647
|
+
bottom: 100%; /* Position above the input */
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.emoji-picker-container.top {
|
|
651
|
+
top: 100%; /* Position below the input */
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
.emoji-button {
|
|
656
|
+
cursor: pointer;
|
|
657
|
+
font-size: 1.25rem; /* text-xl */
|
|
658
|
+
text-align: center;
|
|
659
|
+
padding: 4px; /* p-1 */
|
|
660
|
+
border-radius: 0.25rem; /* rounded */
|
|
661
|
+
background: none; /* No background */
|
|
662
|
+
border: none; /* No border */
|
|
663
|
+
transition: background-color 0.2s ease-in-out;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.emoji-button:hover {
|
|
667
|
+
background-color: #f3f4f6; /* gray-100 */
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.no-results {
|
|
671
|
+
text-align: center;
|
|
672
|
+
color: #6b7280; /* gray-500 */
|
|
673
|
+
grid-column: 1 / -1; /* Span across all columns in the grid */
|
|
674
|
+
}
|
|
675
|
+
`}</style>
|
|
676
|
+
</div>
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
export default EmojiInput; // Export the component with the new name
|
|
@@ -86,7 +86,7 @@ const InstructorRightBar = ({ planState }) => {
|
|
|
86
86
|
marginBottom: "40px",
|
|
87
87
|
}}
|
|
88
88
|
onClick={() => {
|
|
89
|
-
window.location.href = `${protocol}//${host}${port}/
|
|
89
|
+
window.location.href = `${protocol}//${host}${port}/instructor/courses/create-course`;
|
|
90
90
|
}}
|
|
91
91
|
/>
|
|
92
92
|
</RecentAddedEnterprie>
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import useAxios from "axios-hooks";
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
|
|
4
|
-
const useHeader = () => {
|
|
4
|
+
const useHeader = (props = { default: false }) => {
|
|
5
5
|
// get user details
|
|
6
|
-
const [
|
|
6
|
+
const [userDetails, getUserDetails] = useAxios(
|
|
7
|
+
{
|
|
8
|
+
method: "get",
|
|
9
|
+
url: "/iam/v1/users/me/",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
manual: !props.default,
|
|
13
|
+
}
|
|
14
|
+
);
|
|
7
15
|
|
|
8
16
|
const handleGetUserDetails = async () => {
|
|
9
17
|
await getUserDetails({
|
|
@@ -369,15 +377,11 @@ const useHeader = () => {
|
|
|
369
377
|
}
|
|
370
378
|
};
|
|
371
379
|
|
|
372
|
-
//defaults
|
|
373
|
-
// get user info
|
|
374
|
-
useEffect(() => {
|
|
375
|
-
handleGetUserDetails();
|
|
376
|
-
}, []);
|
|
377
|
-
|
|
378
380
|
return {
|
|
379
381
|
handleGetUserDetails,
|
|
380
382
|
userDetails,
|
|
383
|
+
user: userDetails.data,
|
|
384
|
+
getUserDetails,
|
|
381
385
|
handleGetUserAccountsDetail,
|
|
382
386
|
userAccountsDetail,
|
|
383
387
|
handleGetDefaultAccount,
|
|
@@ -398,7 +402,7 @@ const useHeader = () => {
|
|
|
398
402
|
handleGetAllAffiliate,
|
|
399
403
|
handleSetDefaultAffiliate,
|
|
400
404
|
setDefaultAffiliateData,
|
|
401
|
-
|
|
405
|
+
plan: userPlanData?.data,
|
|
402
406
|
userPlanData,
|
|
403
407
|
handleGetUserPlan,
|
|
404
408
|
getCurrentSubscriptionData,
|
|
@@ -56,9 +56,7 @@ const HeaderComponent = (props) => {
|
|
|
56
56
|
// in general data find user info and replace where user details is being used
|
|
57
57
|
const {
|
|
58
58
|
handleGetUserAccountsDetail,
|
|
59
|
-
handleGetUserDetails,
|
|
60
59
|
userAccountsDetail,
|
|
61
|
-
userDetails,
|
|
62
60
|
// getDefaultAccount,
|
|
63
61
|
// handleGetDefaultAccount,
|
|
64
62
|
unreadNotificationData,
|
|
@@ -75,8 +73,13 @@ const HeaderComponent = (props) => {
|
|
|
75
73
|
handleSetDefaultAccount,
|
|
76
74
|
} = useHeader();
|
|
77
75
|
const { pathname } = useLocation();
|
|
78
|
-
const {
|
|
79
|
-
|
|
76
|
+
const {
|
|
77
|
+
setGeneralData,
|
|
78
|
+
generalData,
|
|
79
|
+
notificationMarkReadData,
|
|
80
|
+
accountName,
|
|
81
|
+
userDetails,
|
|
82
|
+
} = useContext(OutletContext);
|
|
80
83
|
const [accountType, setAccountType] = useState("");
|
|
81
84
|
const currentAccountType = props?.selectedAccount?.type
|
|
82
85
|
? props?.selectedAccount?.type?.toLowerCase()
|
|
@@ -91,7 +94,6 @@ const HeaderComponent = (props) => {
|
|
|
91
94
|
useEffect(() => {
|
|
92
95
|
setIsOpen(false);
|
|
93
96
|
handleGetUserAccountsDetail();
|
|
94
|
-
handleGetUserDetails();
|
|
95
97
|
props?.handleGetDefaultAccount();
|
|
96
98
|
}, []);
|
|
97
99
|
|
package/src/components/index.js
CHANGED
|
@@ -56,3 +56,4 @@ export { default as DeveloperBanner } from "./banner/developerBanner";
|
|
|
56
56
|
export { default as VideoPlayer } from "./video-player";
|
|
57
57
|
export { default as useAudioPlayer } from "./useAudioPlayer";
|
|
58
58
|
export { default as AudioWaveComponent } from "./useAudioPlayer/audioWave";
|
|
59
|
+
export { default as InputEmoji } from "./InputEmoji";
|
|
@@ -98,7 +98,6 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
const { accountName, planState } = useContext(OutletContext);
|
|
101
|
-
|
|
102
101
|
// handle if account does not have subscription but is an affiliate
|
|
103
102
|
const prioritizedAffiliateId = useMemo(() => {
|
|
104
103
|
let prioritizedAffiliateId = null;
|
|
@@ -107,11 +106,13 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
107
106
|
// Priority 1: Use cookie value if it exists
|
|
108
107
|
prioritizedAffiliateId = cookieValue;
|
|
109
108
|
} else if (
|
|
110
|
-
|
|
109
|
+
planState !== "ACTIVE" &&
|
|
110
|
+
planState !== "GRACE PERIOD" && // Check for NO subscription
|
|
111
111
|
getAllAffiliateData?.data?.results?.length > 0 // Check if the list exists and is not empty
|
|
112
112
|
) {
|
|
113
113
|
// Priority 2: If no cookie, no subscription, and list exists...
|
|
114
114
|
// Assign the ID of the *first* affiliate directly from the results array
|
|
115
|
+
setCookie(getAllAffiliateData.data.results[0]?.id);
|
|
115
116
|
prioritizedAffiliateId = getAllAffiliateData.data.results[0]?.id; // Optional chaining on 'id'
|
|
116
117
|
}
|
|
117
118
|
return prioritizedAffiliateId;
|
|
@@ -188,28 +189,30 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
188
189
|
<div className="left">
|
|
189
190
|
{switchValue !== "affiliates" && <p>{accountName}</p>}
|
|
190
191
|
{switchValue === "affiliates" && (
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
192
|
+
<OutsideAlerter handleClick={() => setDropdown(false)}>
|
|
193
|
+
<AffiliatesDropDown>
|
|
194
|
+
{!selectedValue ? (
|
|
195
|
+
<div
|
|
196
|
+
className="placeholder"
|
|
197
|
+
onClick={() => setDropdown(!dropdown)}
|
|
198
|
+
>
|
|
199
|
+
<p>Select affiliate</p>
|
|
200
|
+
<ArrowDown />
|
|
201
|
+
</div>
|
|
202
|
+
) : (
|
|
203
|
+
<div
|
|
204
|
+
className="selected"
|
|
205
|
+
onClick={() => setDropdown(!dropdown)}
|
|
206
|
+
>
|
|
207
|
+
<div>
|
|
208
|
+
<img src={selectedValue?.image} alt="" />
|
|
209
|
+
<p>{selectedValue?.name}</p>
|
|
210
|
+
</div>
|
|
211
|
+
<ArrowDown />
|
|
206
212
|
</div>
|
|
207
|
-
|
|
208
|
-
</div>
|
|
209
|
-
)}
|
|
213
|
+
)}
|
|
210
214
|
|
|
211
|
-
|
|
212
|
-
<OutsideAlerter handleClick={() => setDropdown(false)}>
|
|
215
|
+
{dropdown && switchValue === "affiliates" ? (
|
|
213
216
|
<AffiliatesDropDownWrapper>
|
|
214
217
|
<div className="search_wrapper">
|
|
215
218
|
<Search
|
|
@@ -223,7 +226,8 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
223
226
|
key={item?.id}
|
|
224
227
|
onClick={() => {
|
|
225
228
|
handleSelection(item);
|
|
226
|
-
}}
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
227
231
|
<div className="left">
|
|
228
232
|
<img src={item?.image} alt="" />
|
|
229
233
|
<p>{item?.name}</p>
|
|
@@ -233,24 +237,26 @@ const InstructorAccountSwitcher = ({ generalData, onChange }) => {
|
|
|
233
237
|
))}
|
|
234
238
|
</ul>
|
|
235
239
|
</AffiliatesDropDownWrapper>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
</
|
|
240
|
+
) : null}
|
|
241
|
+
</AffiliatesDropDown>
|
|
242
|
+
</OutsideAlerter>
|
|
239
243
|
)}
|
|
240
244
|
</div>
|
|
241
245
|
{
|
|
242
246
|
<div className="btn__wrapper">
|
|
243
|
-
{
|
|
247
|
+
{(planState === "ACTIVE" || planState === "GRACE PERIOD") && (
|
|
244
248
|
<button
|
|
245
249
|
className={switchValue !== "affiliates" ? "active" : ""}
|
|
246
|
-
onClick={() => handleSwitch(1)}
|
|
250
|
+
onClick={() => handleSwitch(1)}
|
|
251
|
+
>
|
|
247
252
|
<span>My Account</span>
|
|
248
253
|
<span className="circle"></span>
|
|
249
254
|
</button>
|
|
250
255
|
)}
|
|
251
256
|
<button
|
|
252
257
|
className={switchValue === "affiliates" ? "active" : ""}
|
|
253
|
-
onClick={() => handleSwitch(2)}
|
|
258
|
+
onClick={() => handleSwitch(2)}
|
|
259
|
+
>
|
|
254
260
|
<span>Affiliates</span>
|
|
255
261
|
<span className="circle"></span>
|
|
256
262
|
</button>
|
|
@@ -354,6 +360,7 @@ const AffiliatesDropDown = styled.div`
|
|
|
354
360
|
img {
|
|
355
361
|
width: 36px;
|
|
356
362
|
height: 36px;
|
|
363
|
+
border-radius: 50%;
|
|
357
364
|
}
|
|
358
365
|
p {
|
|
359
366
|
color: #00c2c2;
|
|
@@ -407,6 +414,8 @@ const AffiliatesDropDownWrapper = styled.div`
|
|
|
407
414
|
img {
|
|
408
415
|
width: 20px;
|
|
409
416
|
height: 20px;
|
|
417
|
+
border-radius: 50%;
|
|
418
|
+
object-fit: cover;
|
|
410
419
|
}
|
|
411
420
|
}
|
|
412
421
|
span {
|
|
@@ -288,7 +288,7 @@ const SideMenu = ({
|
|
|
288
288
|
}
|
|
289
289
|
}}
|
|
290
290
|
key={index}
|
|
291
|
-
className={cx(
|
|
291
|
+
className={cx({ active, [route.text]: true, disabled: route.disabled })}
|
|
292
292
|
minimal={isOpen}>
|
|
293
293
|
<IconContainer active={active}>
|
|
294
294
|
{active ? iconActive : icon}
|
|
@@ -348,6 +348,7 @@ SideMenu.propTypes = {
|
|
|
348
348
|
icon: PropTypes.element.isRequired,
|
|
349
349
|
text: PropTypes.string.isRequired,
|
|
350
350
|
notifications: PropTypes.number,
|
|
351
|
+
disabled: PropTypes.bool,
|
|
351
352
|
})
|
|
352
353
|
).isRequired,
|
|
353
354
|
})
|
|
@@ -12,14 +12,12 @@ import Loader from "../../loader";
|
|
|
12
12
|
import avatar from "../../../assets/images/avatar.png";
|
|
13
13
|
import { OutletContext } from "../../AppMainLayout";
|
|
14
14
|
|
|
15
|
-
const UserCard = ({
|
|
16
|
-
const {
|
|
17
|
-
useHeader();
|
|
15
|
+
const UserCard = ({ isOpen, findText }) => {
|
|
16
|
+
const { handleSetDefaultAccount } = useHeader();
|
|
18
17
|
const [organizationName, setOrganizationName] = useState();
|
|
19
|
-
const { generalData, accountName } = useContext(OutletContext);
|
|
18
|
+
const { generalData, accountName, userDetails } = useContext(OutletContext);
|
|
20
19
|
|
|
21
20
|
useEffect(() => {
|
|
22
|
-
handleGetUserDetails();
|
|
23
21
|
if (generalData?.selectedAccount) {
|
|
24
22
|
const date = new Date();
|
|
25
23
|
date.setDate(date.getDate() + 28);
|
|
@@ -71,9 +69,9 @@ const UserCard = ({ user, isOpen, findText }) => {
|
|
|
71
69
|
src={generalData?.selectedAccount?.profile_photo?.url || avatar}
|
|
72
70
|
isOpen={isOpen}
|
|
73
71
|
onClick={() => {
|
|
74
|
-
if (window.location.pathname.includes("personal")) {
|
|
75
|
-
|
|
76
|
-
}
|
|
72
|
+
// if (window.location.pathname.includes("personal")) {
|
|
73
|
+
// window.location.href = `${window.location.protocol}//${window.location.hostname}/personal/profile`;
|
|
74
|
+
// }
|
|
77
75
|
if (window.location.pathname.includes("enterprise")) {
|
|
78
76
|
window.location.href = `${window.location.protocol}//${window.location.hostname}/enterprise/profile`;
|
|
79
77
|
}
|
|
@@ -179,6 +179,7 @@ export const sideMenuOptions = [
|
|
|
179
179
|
icon: <AwardIcon />,
|
|
180
180
|
iconActive: <AwardIconActive />,
|
|
181
181
|
text: "Manage report",
|
|
182
|
+
// disabled: true,
|
|
182
183
|
},
|
|
183
184
|
{
|
|
184
185
|
path: "/instructor/courses",
|
|
@@ -228,12 +229,14 @@ export const sideMenuOptions = [
|
|
|
228
229
|
icon: <DashboardIcon />,
|
|
229
230
|
iconActive: <DashboardIconActive />,
|
|
230
231
|
text: "Dashboard",
|
|
232
|
+
disabled: true,
|
|
231
233
|
},
|
|
232
234
|
{
|
|
233
235
|
path: "/personal/library",
|
|
234
236
|
icon: <LibraryIcon />,
|
|
235
237
|
iconActive: <LibraryIconActive />,
|
|
236
238
|
text: "Library",
|
|
239
|
+
disabled: true,
|
|
237
240
|
},
|
|
238
241
|
{
|
|
239
242
|
path: "/personal/courses",
|
|
@@ -259,6 +262,7 @@ export const sideMenuOptions = [
|
|
|
259
262
|
icon: <AddOnIcon />,
|
|
260
263
|
iconActive: <AddOnIconActive />,
|
|
261
264
|
text: "Add on",
|
|
265
|
+
disabled: true,
|
|
262
266
|
},
|
|
263
267
|
],
|
|
264
268
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import useAxios from "axios-hooks";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
|
|
3
3
|
import sound from "./new-notification-7-210334.mp3";
|
|
4
|
+
import { cookieGrabber, useCookiePolling } from "../utils/cookiePolling";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Represents the details of the last message in a chat room overview.
|
|
@@ -236,38 +237,28 @@ import sound from "./new-notification-7-210334.mp3";
|
|
|
236
237
|
* @property {any | null} media - The media data (e.g., File object, FormData details). Needs proper handling before API call.
|
|
237
238
|
*/
|
|
238
239
|
|
|
239
|
-
const useMessageKit = (
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
if (!string) return "";
|
|
248
|
-
return string.split("=")[1].trim();
|
|
240
|
+
const useMessageKit = () => {
|
|
241
|
+
const selectedAccountId = useCookiePolling("defaultAccountID");
|
|
242
|
+
const defaultAccountType = useCookiePolling("defaultAccountType");
|
|
243
|
+
const selectedAccount = {
|
|
244
|
+
id: selectedAccountId,
|
|
245
|
+
type: defaultAccountType,
|
|
249
246
|
};
|
|
250
|
-
const
|
|
251
|
-
() => ({
|
|
252
|
-
id: cookieGrabber("defaultAccountID"),
|
|
253
|
-
type: cookieGrabber("defaultAccountType"),
|
|
254
|
-
}),
|
|
255
|
-
[]
|
|
256
|
-
);
|
|
247
|
+
const token = useCookiePolling("access");
|
|
257
248
|
const buildSocketUrl = useCallback(() => {
|
|
258
249
|
const baseUrl =
|
|
259
250
|
window.location.hostname.includes("staging") ||
|
|
260
251
|
window.location.hostname.includes("localhost")
|
|
261
252
|
? "https://dev-117782726-api.learngual.com"
|
|
262
253
|
: "https://api.learngual.com";
|
|
263
|
-
|
|
254
|
+
|
|
264
255
|
return (
|
|
265
256
|
baseUrl.replace("http", "ws") +
|
|
266
257
|
`/notify/v1/ws/connect/?authorization=${token}&_account=${selectedAccount.id}`
|
|
267
258
|
);
|
|
268
259
|
}, []);
|
|
269
260
|
|
|
270
|
-
const affiliateAccount =
|
|
261
|
+
const affiliateAccount = useCookiePolling("affiliateAccount", 1000);
|
|
271
262
|
// console.log(affiliateAccount, affiliatesActive, "affiliate");
|
|
272
263
|
// get account type
|
|
273
264
|
const [, request] = useAxios(
|
|
@@ -370,14 +361,13 @@ const useMessageKit = ({ affiliateAccount: AffiliateAccount }) => {
|
|
|
370
361
|
});
|
|
371
362
|
}
|
|
372
363
|
|
|
373
|
-
if (
|
|
374
|
-
selectedAccount.type.toLowerCase() === "instructor" &&
|
|
375
|
-
affiliateAccount
|
|
376
|
-
// &&
|
|
377
|
-
// affiliatesActive
|
|
378
|
-
) {
|
|
364
|
+
if (selectedAccount.type.toLowerCase() === "instructor") {
|
|
379
365
|
response = await request({
|
|
380
|
-
url:
|
|
366
|
+
url:
|
|
367
|
+
nextPage ||
|
|
368
|
+
`/notify/v1/instructor${
|
|
369
|
+
affiliateAccount ? "/".concat(affiliateAccount) : ""
|
|
370
|
+
}/rooms/`,
|
|
381
371
|
params: {
|
|
382
372
|
limit,
|
|
383
373
|
_account: selectedAccount.id,
|
|
@@ -438,14 +428,13 @@ const useMessageKit = ({ affiliateAccount: AffiliateAccount }) => {
|
|
|
438
428
|
});
|
|
439
429
|
}
|
|
440
430
|
|
|
441
|
-
if (
|
|
442
|
-
String(selectedAccount.type).toLowerCase() === "instructor" &&
|
|
443
|
-
affiliateAccount
|
|
444
|
-
// &&
|
|
445
|
-
// affiliatesActive
|
|
446
|
-
) {
|
|
431
|
+
if (String(selectedAccount.type).toLowerCase() === "instructor") {
|
|
447
432
|
response = await request({
|
|
448
|
-
url:
|
|
433
|
+
url:
|
|
434
|
+
nextPage ||
|
|
435
|
+
`/notify/v1/instructor${
|
|
436
|
+
affiliateAccount ? "/".concat(affiliateAccount) : ""
|
|
437
|
+
}/chats/`,
|
|
449
438
|
params: {
|
|
450
439
|
_account: selectedAccount.id,
|
|
451
440
|
room_id: roomId,
|
|
@@ -585,12 +574,11 @@ const useMessageKit = ({ affiliateAccount: AffiliateAccount }) => {
|
|
|
585
574
|
url: `/notify/v1/enterprise/chats/${latestMessageId}/read_message/`,
|
|
586
575
|
});
|
|
587
576
|
}
|
|
588
|
-
if (
|
|
589
|
-
String(selectedAccount.type).toLowerCase() === "instructor" &&
|
|
590
|
-
affiliateAccount
|
|
591
|
-
) {
|
|
577
|
+
if (String(selectedAccount.type).toLowerCase() === "instructor") {
|
|
592
578
|
await request({
|
|
593
|
-
url: `/notify/v1/instructor
|
|
579
|
+
url: `/notify/v1/instructor${
|
|
580
|
+
affiliateAccount ? "/".concat(affiliateAccount) : ""
|
|
581
|
+
}/chats/${latestMessageId}/read_message/`,
|
|
594
582
|
});
|
|
595
583
|
}
|
|
596
584
|
if (String(selectedAccount.type).toLowerCase() === "personal") {
|
|
@@ -885,11 +873,12 @@ const useMessageKit = ({ affiliateAccount: AffiliateAccount }) => {
|
|
|
885
873
|
method: "Post",
|
|
886
874
|
});
|
|
887
875
|
} else if (
|
|
888
|
-
String(selectedAccount.type).toLowerCase() === "instructor"
|
|
889
|
-
affiliateAccount
|
|
876
|
+
String(selectedAccount.type).toLowerCase() === "instructor"
|
|
890
877
|
) {
|
|
891
878
|
response = await request({
|
|
892
|
-
url: `/notify/v1/instructor
|
|
879
|
+
url: `/notify/v1/instructor${
|
|
880
|
+
affiliateAccount ? "/".concat(affiliateAccount) : ""
|
|
881
|
+
}/chats/`,
|
|
893
882
|
params: requestParams,
|
|
894
883
|
data: requestData,
|
|
895
884
|
method: "Post",
|
|
@@ -1295,14 +1284,11 @@ const useMessageKit = ({ affiliateAccount: AffiliateAccount }) => {
|
|
|
1295
1284
|
method: "patch",
|
|
1296
1285
|
});
|
|
1297
1286
|
}
|
|
1298
|
-
if (
|
|
1299
|
-
selectedAccount.type.toLowerCase() === "instructor" &&
|
|
1300
|
-
affiliateAccount
|
|
1301
|
-
// &&
|
|
1302
|
-
// affiliatesActive
|
|
1303
|
-
) {
|
|
1287
|
+
if (selectedAccount.type.toLowerCase() === "instructor") {
|
|
1304
1288
|
response = await request({
|
|
1305
|
-
url: `/notify/v1/instructor
|
|
1289
|
+
url: `/notify/v1/instructor${
|
|
1290
|
+
affiliateAccount ? "/".concat(affiliateAccount) : ""
|
|
1291
|
+
}/rooms/${roomId}/change_pin_status/`,
|
|
1306
1292
|
method: "patch",
|
|
1307
1293
|
});
|
|
1308
1294
|
} // incomplete
|
|
@@ -1375,14 +1361,11 @@ const useMessageKit = ({ affiliateAccount: AffiliateAccount }) => {
|
|
|
1375
1361
|
method: "delete",
|
|
1376
1362
|
});
|
|
1377
1363
|
}
|
|
1378
|
-
if (
|
|
1379
|
-
selectedAccount.type.toLowerCase() === "instructor" &&
|
|
1380
|
-
affiliateAccount
|
|
1381
|
-
// &&
|
|
1382
|
-
// affiliatesActive
|
|
1383
|
-
) {
|
|
1364
|
+
if (selectedAccount.type.toLowerCase() === "instructor") {
|
|
1384
1365
|
await request({
|
|
1385
|
-
url: `/notify/v1/instructor
|
|
1366
|
+
url: `/notify/v1/instructor${
|
|
1367
|
+
affiliateAccount ? "/".concat(affiliateAccount) : ""
|
|
1368
|
+
}/rooms/${roomId}/`,
|
|
1386
1369
|
method: "delete",
|
|
1387
1370
|
});
|
|
1388
1371
|
} // incomplete
|
|
@@ -1419,12 +1402,11 @@ const useMessageKit = ({ affiliateAccount: AffiliateAccount }) => {
|
|
|
1419
1402
|
data,
|
|
1420
1403
|
});
|
|
1421
1404
|
}
|
|
1422
|
-
if (
|
|
1423
|
-
selectedAccount.type.toLowerCase() === "instructor" &&
|
|
1424
|
-
affiliateAccount
|
|
1425
|
-
) {
|
|
1405
|
+
if (selectedAccount.type.toLowerCase() === "instructor") {
|
|
1426
1406
|
response = await request({
|
|
1427
|
-
url: `/notify/v1/instructor
|
|
1407
|
+
url: `/notify/v1/instructor${
|
|
1408
|
+
affiliateAccount ? "/".concat(affiliateAccount) : ""
|
|
1409
|
+
}/chats/reports/`,
|
|
1428
1410
|
method: "post",
|
|
1429
1411
|
data,
|
|
1430
1412
|
});
|
|
@@ -1441,6 +1423,7 @@ const useMessageKit = ({ affiliateAccount: AffiliateAccount }) => {
|
|
|
1441
1423
|
// Initial data fetch effect
|
|
1442
1424
|
useEffect(() => {
|
|
1443
1425
|
(async () => {
|
|
1426
|
+
if (!selectedAccountId || !defaultAccountType || !token) return;
|
|
1444
1427
|
await getMessageRoomsByCourses();
|
|
1445
1428
|
// reportChat({
|
|
1446
1429
|
// reasons: ["Verbal harassment"],
|
|
@@ -1462,7 +1445,7 @@ const useMessageKit = ({ affiliateAccount: AffiliateAccount }) => {
|
|
|
1462
1445
|
// accountId: "24dbaeede1",
|
|
1463
1446
|
// });
|
|
1464
1447
|
})();
|
|
1465
|
-
}, [
|
|
1448
|
+
}, [affiliateAccount, token, defaultAccountType, selectedAccountId]); // Rerun when selectedAccount changes
|
|
1466
1449
|
// console.log(
|
|
1467
1450
|
// getMessagesForRoom("5be9b48281ac4c2885d3b719654ed59d", "878", "24dbaeede1"),
|
|
1468
1451
|
// "hold versions"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
//helper function to get cookie
|
|
4
|
+
export const cookieGrabber = (key = "") => {
|
|
5
|
+
const cookies = document.cookie;
|
|
6
|
+
const string = cookies.split(";").find((cookie) => {
|
|
7
|
+
cookie = cookie.trim();
|
|
8
|
+
if (cookie.startsWith(key.trim() + "=")) return true;
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
if (!string) return "";
|
|
12
|
+
return string.split("=")[1].trim();
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Custom hook to poll a specific cookie and update state on change.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} cookieName The name of the cookie to monitor.
|
|
19
|
+
* @param {number} intervalMs The polling interval in milliseconds (e.g., 1000 for 1 second).
|
|
20
|
+
* @returns {string | null} The current value of the cookie.
|
|
21
|
+
*/
|
|
22
|
+
export function useCookiePolling(cookieName, intervalMs = 500) {
|
|
23
|
+
const [cookieValue, setCookieValue] = useState(() =>
|
|
24
|
+
cookieGrabber(cookieName)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const intervalId = setInterval(() => {
|
|
29
|
+
const currentValue = cookieGrabber(cookieName);
|
|
30
|
+
// Update state only if the value has actually changed
|
|
31
|
+
if (currentValue !== cookieValue) {
|
|
32
|
+
console.log(
|
|
33
|
+
`Cookie '${cookieName}' changed from '${cookieValue}' to '${currentValue}'`
|
|
34
|
+
);
|
|
35
|
+
setCookieValue(currentValue);
|
|
36
|
+
}
|
|
37
|
+
}, intervalMs);
|
|
38
|
+
|
|
39
|
+
// Cleanup: clear the interval when the component unmounts or dependencies change
|
|
40
|
+
return () => clearInterval(intervalId);
|
|
41
|
+
}, [cookieName, intervalMs, cookieValue]); // Re-run effect if cookieName or interval changes, or if cookieValue was updated
|
|
42
|
+
|
|
43
|
+
return cookieValue;
|
|
44
|
+
}
|