l-min-components 1.0.886 → 1.0.895
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,8 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useState,
|
|
3
|
-
useEffect,
|
|
4
|
-
createContext,
|
|
5
|
-
} from "react";
|
|
1
|
+
import React, { useState, useEffect, createContext } from "react";
|
|
6
2
|
import { Outlet, useLocation } from "react-router-dom";
|
|
7
3
|
import {
|
|
8
4
|
Layout,
|
|
@@ -16,6 +12,7 @@ import {
|
|
|
16
12
|
Banner,
|
|
17
13
|
SideNav as SideBar,
|
|
18
14
|
SideMenu,
|
|
15
|
+
FullPageLoader,
|
|
19
16
|
} from "../";
|
|
20
17
|
import { leftNavMenu, user, sideMenuOptions } from "../../hooks/leftNavMenu";
|
|
21
18
|
import InstructorAccountSwitcher from "../instructorAccountSwitcher";
|
|
@@ -97,7 +94,6 @@ const AppMainLayout = () => {
|
|
|
97
94
|
// set access token from cookie to context
|
|
98
95
|
|
|
99
96
|
useEffect(() => {
|
|
100
|
-
|
|
101
97
|
let cookieValue = null;
|
|
102
98
|
const cookieName = "access";
|
|
103
99
|
|
|
@@ -172,156 +168,170 @@ const AppMainLayout = () => {
|
|
|
172
168
|
setDefaultLang,
|
|
173
169
|
defaultLang,
|
|
174
170
|
findText,
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
translations,
|
|
172
|
+
isTranslationsLoading,
|
|
173
|
+
setIsTranslationsLoading,
|
|
174
|
+
translateData
|
|
177
175
|
} = useTranslation(wordBank);
|
|
178
|
-
|
|
176
|
+
|
|
179
177
|
return (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
178
|
+
<OutletContext.Provider
|
|
179
|
+
value={{
|
|
180
|
+
findText,
|
|
181
|
+
wordBank,
|
|
182
|
+
defaultLang,
|
|
183
|
+
isTranslationsLoading,
|
|
184
|
+
setIsTranslationsLoading,
|
|
185
|
+
setDefaultLang,
|
|
186
|
+
setRightComponent,
|
|
187
|
+
setRightLayout,
|
|
188
|
+
generalData,
|
|
189
|
+
setGeneralData,
|
|
190
|
+
coming,
|
|
191
|
+
hasLayoutBackgroundImage,
|
|
192
|
+
setHasLayoutBackgroundImage,
|
|
193
|
+
setSideMenuLayout,
|
|
194
|
+
activePage,
|
|
195
|
+
setActivePage,
|
|
196
|
+
studyTab,
|
|
197
|
+
setStudyTab,
|
|
198
|
+
page,
|
|
199
|
+
setPage,
|
|
200
|
+
selectedCourseId,
|
|
201
|
+
setSelectedCourseId,
|
|
202
|
+
centerLayoutStyle,
|
|
203
|
+
setCenterLayoutStyle,
|
|
204
|
+
// return true if instructor affiliates is Active
|
|
205
|
+
setHideAffilicates,
|
|
206
|
+
affiliatesActive,
|
|
207
|
+
accessToken,
|
|
208
|
+
envType,
|
|
209
|
+
newNotifications,
|
|
210
|
+
setNewNotifications,
|
|
211
|
+
notificationMarkReadData,
|
|
212
|
+
handleGetNotificationMarkRead,
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
{/* display mobile layout on device width*/}
|
|
216
|
+
{deviceWidth < 1200 ? (
|
|
217
|
+
<MobileLayout />
|
|
218
|
+
) : (
|
|
219
|
+
<>
|
|
220
|
+
{isTranslationsLoading && Object.keys(translations)?.length === 0 ? (
|
|
221
|
+
<> </>
|
|
222
|
+
) : (
|
|
223
|
+
<Layout
|
|
224
|
+
coming={coming}
|
|
225
|
+
hasLayoutBackgroundImage={hasLayoutBackgroundImage}
|
|
226
|
+
>
|
|
227
|
+
<HeaderComponent setNewNotifications={setNewNotifications} />
|
|
228
|
+
<MainLayout coming={coming}>
|
|
229
|
+
<LeftLayout>
|
|
230
|
+
<SideBar routes={leftNavMenu} />
|
|
231
|
+
{!coming && (
|
|
232
|
+
<>
|
|
233
|
+
{sideMenuLayout && (
|
|
234
|
+
<SideMenu
|
|
235
|
+
user={user}
|
|
236
|
+
routes={sideMenuOptions}
|
|
237
|
+
affiliatesActive={affiliatesActive}
|
|
238
|
+
userType={generalData?.selectedAccount?.type?.toLowerCase()}
|
|
239
|
+
isOpen={isOpen}
|
|
240
|
+
setIsOpen={setIsOpen}
|
|
241
|
+
setRightComponent={setRightComponent}
|
|
242
|
+
planState={planState}
|
|
243
|
+
findText={findText}
|
|
244
|
+
/>
|
|
245
|
+
)}
|
|
246
|
+
</>
|
|
247
|
+
)}
|
|
248
|
+
{
|
|
249
|
+
// window.location.pathname.includes("enterprise")
|
|
250
|
+
// ? "enterprise"
|
|
251
|
+
// : window.location.pathname.includes("personal")
|
|
252
|
+
// ? "personal"
|
|
253
|
+
// : window.location.pathname.includes("instructor")
|
|
254
|
+
// ? "instructor"
|
|
255
|
+
// : window.location.pathname.includes("developer") ||
|
|
256
|
+
// window.location.hostname.includes("developer")
|
|
257
|
+
// ? "developer"
|
|
258
|
+
// : "developer"
|
|
259
|
+
}
|
|
260
|
+
</LeftLayout>
|
|
261
|
+
<CenterLayout isOpen={isOpen} style={centerLayoutStyle}>
|
|
262
|
+
{window.location.pathname.includes("instructor") &&
|
|
263
|
+
!window.location.pathname.includes("enterprise") &&
|
|
264
|
+
!window.location.pathname.includes("manage-teams") &&
|
|
265
|
+
!hideAffilicates && (
|
|
266
|
+
<InstructorAccountSwitcher
|
|
267
|
+
setAccountType={setAffiliatesActive}
|
|
237
268
|
/>
|
|
238
269
|
)}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// : window.location.pathname.includes("instructor")
|
|
247
|
-
// ? "instructor"
|
|
248
|
-
// : window.location.pathname.includes("developer") ||
|
|
249
|
-
// window.location.hostname.includes("developer")
|
|
250
|
-
// ? "developer"
|
|
251
|
-
// : "developer"
|
|
252
|
-
}
|
|
253
|
-
</LeftLayout>
|
|
254
|
-
<CenterLayout isOpen={isOpen} style={centerLayoutStyle}>
|
|
255
|
-
{window.location.pathname.includes("instructor") &&
|
|
256
|
-
!window.location.pathname.includes("enterprise") &&
|
|
257
|
-
!window.location.pathname.includes("manage-teams") &&
|
|
258
|
-
!hideAffilicates && (
|
|
259
|
-
<InstructorAccountSwitcher
|
|
260
|
-
setAccountType={setAffiliatesActive}
|
|
270
|
+
|
|
271
|
+
{window.location.pathname.includes("enterprise") &&
|
|
272
|
+
planState === "GRACE PERIOD" ? (
|
|
273
|
+
<GracePeriod
|
|
274
|
+
getCurrentSubscriptionData={getCurrentSubscriptionData}
|
|
275
|
+
handleCurrentSubscription={handleCurrentSubscription}
|
|
276
|
+
gracePeriod={gracePeriod}
|
|
261
277
|
/>
|
|
278
|
+
) : (
|
|
279
|
+
<Outlet />
|
|
262
280
|
)}
|
|
281
|
+
</CenterLayout>
|
|
263
282
|
|
|
264
|
-
{
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
handleCurrentSubscription={handleCurrentSubscription}
|
|
269
|
-
gracePeriod={gracePeriod}
|
|
270
|
-
/>
|
|
271
|
-
) : (
|
|
272
|
-
<Outlet />
|
|
273
|
-
)}
|
|
274
|
-
</CenterLayout>
|
|
275
|
-
|
|
276
|
-
{rightLayout && !coming && (
|
|
277
|
-
<RightLayout>
|
|
278
|
-
{rightComponent ??
|
|
279
|
-
(window.location.pathname.includes("enterprise") &&
|
|
280
|
-
!window.location.pathname.includes("file-manager") ? (
|
|
281
|
-
<EnterpriseRightBar
|
|
282
|
-
gracePeriod={gracePeriod}
|
|
283
|
-
setGracePeriod={setGracePeriod}
|
|
284
|
-
planState={planState}
|
|
285
|
-
/>
|
|
286
|
-
) : window.location.pathname.includes(
|
|
287
|
-
"personal/dashboard"
|
|
288
|
-
) ? (
|
|
289
|
-
<PersonalRightBar />
|
|
290
|
-
) : window.location.pathname.includes("personal/addons") ? (
|
|
291
|
-
<InstructorRightBar personal />
|
|
292
|
-
) : window.location.pathname.includes(
|
|
293
|
-
"personal/courses"
|
|
294
|
-
) ? (
|
|
295
|
-
<InstructorRightBar personal />
|
|
296
|
-
) : window.location.pathname.includes(
|
|
297
|
-
"personal/library/selectlanguage"
|
|
298
|
-
) ? (
|
|
299
|
-
<InstructorRightBar personal />
|
|
300
|
-
) : window.location.pathname.includes(
|
|
301
|
-
"personal/library"
|
|
302
|
-
) ? (
|
|
303
|
-
<PersonalRightBar personalLibrary />
|
|
304
|
-
) : window.location.pathname.includes(
|
|
305
|
-
"personal/reports"
|
|
306
|
-
) ? (
|
|
307
|
-
<PersonalRightBar personalReport />
|
|
308
|
-
) : window.location.pathname.includes("instructor") &&
|
|
309
|
-
!window.location.pathname.includes("manage-teams") &&
|
|
283
|
+
{rightLayout && !coming && (
|
|
284
|
+
<RightLayout>
|
|
285
|
+
{rightComponent ??
|
|
286
|
+
(window.location.pathname.includes("enterprise") &&
|
|
310
287
|
!window.location.pathname.includes("file-manager") ? (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
288
|
+
<EnterpriseRightBar
|
|
289
|
+
gracePeriod={gracePeriod}
|
|
290
|
+
setGracePeriod={setGracePeriod}
|
|
291
|
+
planState={planState}
|
|
292
|
+
/>
|
|
293
|
+
) : window.location.pathname.includes(
|
|
294
|
+
"personal/dashboard"
|
|
295
|
+
) ? (
|
|
296
|
+
<PersonalRightBar />
|
|
297
|
+
) : window.location.pathname.includes(
|
|
298
|
+
"personal/addons"
|
|
299
|
+
) ? (
|
|
300
|
+
<InstructorRightBar personal />
|
|
301
|
+
) : window.location.pathname.includes(
|
|
302
|
+
"personal/courses"
|
|
303
|
+
) ? (
|
|
304
|
+
<InstructorRightBar personal />
|
|
305
|
+
) : window.location.pathname.includes(
|
|
306
|
+
"personal/library/selectlanguage"
|
|
307
|
+
) ? (
|
|
308
|
+
<InstructorRightBar personal />
|
|
309
|
+
) : window.location.pathname.includes(
|
|
310
|
+
"personal/library"
|
|
311
|
+
) ? (
|
|
312
|
+
<PersonalRightBar personalLibrary />
|
|
313
|
+
) : window.location.pathname.includes(
|
|
314
|
+
"personal/reports"
|
|
315
|
+
) ? (
|
|
316
|
+
<PersonalRightBar personalReport />
|
|
317
|
+
) : window.location.pathname.includes("instructor") &&
|
|
318
|
+
!window.location.pathname.includes("manage-teams") &&
|
|
319
|
+
!window.location.pathname.includes("file-manager") ? (
|
|
320
|
+
<InstructorRightBar />
|
|
321
|
+
) : window.location.pathname.includes("developer") ? (
|
|
322
|
+
<Banner />
|
|
323
|
+
) : (
|
|
324
|
+
<Banner />
|
|
325
|
+
))}
|
|
326
|
+
{/* {rightComponent ?? <Banner />} */}
|
|
327
|
+
</RightLayout>
|
|
328
|
+
)}
|
|
329
|
+
</MainLayout>
|
|
330
|
+
</Layout>
|
|
331
|
+
)}
|
|
332
|
+
</>
|
|
333
|
+
)}
|
|
334
|
+
</OutletContext.Provider>
|
|
325
335
|
);
|
|
326
336
|
};
|
|
327
337
|
|
|
@@ -67,6 +67,8 @@ const HeaderComponent = (props) => {
|
|
|
67
67
|
setGeneralData,
|
|
68
68
|
generalData,
|
|
69
69
|
notificationMarkReadData,
|
|
70
|
+
findText,
|
|
71
|
+
isTranslationsLoading
|
|
70
72
|
} = useContext(OutletContext);
|
|
71
73
|
const [selectedAccount, setSelectedAccount] = useState();
|
|
72
74
|
const { setDefaultAccount, handleSetDefaultAccount } = useHeader();
|
|
@@ -85,8 +87,9 @@ const HeaderComponent = (props) => {
|
|
|
85
87
|
"Report",
|
|
86
88
|
"Created",
|
|
87
89
|
"Report",
|
|
90
|
+
"Password mismatch"
|
|
88
91
|
];
|
|
89
|
-
const {
|
|
92
|
+
const { translateData, defaultLang, setDefaultLang } = useTranslation(wordBank);
|
|
90
93
|
|
|
91
94
|
useEffect(() => {
|
|
92
95
|
setIsOpen(false);
|
|
@@ -415,7 +418,7 @@ const HeaderComponent = (props) => {
|
|
|
415
418
|
|
|
416
419
|
return (
|
|
417
420
|
<Navbar>
|
|
418
|
-
{
|
|
421
|
+
{isTranslationsLoading && <FullPageLoader hasBackground={true} />}
|
|
419
422
|
|
|
420
423
|
<img src={logo} alt="Learngual logo" />
|
|
421
424
|
<Nav
|
|
@@ -528,7 +531,7 @@ const HeaderComponent = (props) => {
|
|
|
528
531
|
setIsOpen();
|
|
529
532
|
}}
|
|
530
533
|
>
|
|
531
|
-
<img src={
|
|
534
|
+
<img src={ currentFlag || usFlag} alt="" />
|
|
532
535
|
<ArrowDownIcon />
|
|
533
536
|
</div>
|
|
534
537
|
{languageDropdown && (
|
|
@@ -24,7 +24,13 @@ const LanguageDropdown = ({ languageDropdown, setLanguageDropdown }) => {
|
|
|
24
24
|
const {
|
|
25
25
|
setGeneralData,
|
|
26
26
|
generalData,
|
|
27
|
+
wordBank,
|
|
28
|
+
findText,
|
|
29
|
+
defaultLang,
|
|
30
|
+
setDefaultLang,
|
|
31
|
+
setIsTranslationsLoading
|
|
27
32
|
} = useContext(OutletContext);
|
|
33
|
+
|
|
28
34
|
const {
|
|
29
35
|
retrieveUserDetailsData,
|
|
30
36
|
handleRetrieveUserDetails,
|
|
@@ -32,12 +38,8 @@ const LanguageDropdown = ({ languageDropdown, setLanguageDropdown }) => {
|
|
|
32
38
|
handleUpdateUserAccount,
|
|
33
39
|
} = useHeader();
|
|
34
40
|
const {
|
|
35
|
-
findText,
|
|
36
41
|
translateData,
|
|
37
|
-
defaultLang,
|
|
38
|
-
setDefaultLang,
|
|
39
42
|
handleTranslate,
|
|
40
|
-
wordsToTranslate,
|
|
41
43
|
} = useTranslation();
|
|
42
44
|
|
|
43
45
|
const languagesData = [
|
|
@@ -99,12 +101,14 @@ const LanguageDropdown = ({ languageDropdown, setLanguageDropdown }) => {
|
|
|
99
101
|
}
|
|
100
102
|
setLanguageDropdown();
|
|
101
103
|
setDefaultLang(item?.slug);
|
|
102
|
-
handleTranslate(item?.slug,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
// handleTranslate(item?.slug, wordBank)
|
|
105
|
+
item?.slug === defaultLang
|
|
106
|
+
? setIsTranslationsLoading(false)
|
|
107
|
+
: setIsTranslationsLoading(true);
|
|
106
108
|
localStorage.setItem("defaultLang", JSON.stringify(item));
|
|
107
109
|
console.log("general id", generalData?.defaultAccount?.id)
|
|
110
|
+
console.log("🚀 ~ handleLanguageChange ~ item?.slug:", item?.slug, defaultLang)
|
|
111
|
+
|
|
108
112
|
|
|
109
113
|
}
|
|
110
114
|
|
|
@@ -27,12 +27,13 @@ const SideMenu = ({
|
|
|
27
27
|
affiliatesActive,
|
|
28
28
|
setRightComponent,
|
|
29
29
|
planState,
|
|
30
|
+
findText
|
|
30
31
|
}) => {
|
|
31
32
|
//console.log("user type", userType);
|
|
32
33
|
// const [isOpen, setIsOpen] = useState(false);
|
|
33
34
|
const [gracePeriod, setGracePeriod] = useState(true);
|
|
34
35
|
|
|
35
|
-
const { setGeneralData, generalData
|
|
36
|
+
const { setGeneralData, generalData } = useContext(OutletContext);
|
|
36
37
|
// const { findText } = useTranslation();
|
|
37
38
|
const onToggle = () => {
|
|
38
39
|
setIsOpen(!isOpen);
|
|
@@ -6,27 +6,14 @@ const useTranslation = (initialSentences = []) => {
|
|
|
6
6
|
|
|
7
7
|
const [defaultLang, setDefaultLang] = useState(value?.slug ?? "en");
|
|
8
8
|
const [translations, setTranslations] = useState({}); // returned translation from backend
|
|
9
|
-
const [
|
|
10
|
-
const isTranslationsComplete = Object.keys(translations)?.length === 0;
|
|
11
|
-
console.log("🚀 ~ useTranslation ~ translations:", translations)
|
|
12
|
-
console.log("🚀 ~ useTranslation ~ wordsToTranslate:", wordsToTranslate)
|
|
9
|
+
const [isTranslationsLoading, setIsTranslationsLoading] = useState(true)
|
|
13
10
|
|
|
14
11
|
|
|
15
12
|
const findText = useCallback(
|
|
16
13
|
(word, kwargs = {}) => {
|
|
17
|
-
// Check if the word is not already translated and not already in the list to avoid duplicates
|
|
18
|
-
if (!translations[word] && !wordsToTranslate.includes(word)) {
|
|
19
|
-
setWordsToTranslate((prevWords) => {
|
|
20
|
-
// Ensure the word is only added if it's not already in the list
|
|
21
|
-
if (!prevWords.includes(word)) {
|
|
22
|
-
return [...prevWords, word];
|
|
23
|
-
}
|
|
24
|
-
return prevWords;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
14
|
return formatSentence(translations[word] || word, kwargs); // Return translated word or original word if not yet translated
|
|
28
15
|
},
|
|
29
|
-
[translations
|
|
16
|
+
[translations]
|
|
30
17
|
);
|
|
31
18
|
|
|
32
19
|
// api request for sending data to backend
|
|
@@ -55,39 +42,32 @@ const useTranslation = (initialSentences = []) => {
|
|
|
55
42
|
}
|
|
56
43
|
};
|
|
57
44
|
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (initialSentences.length > 0) {
|
|
60
|
-
// Add initial sentences while ensuring no duplicates
|
|
61
|
-
const uniqueSentences = initialSentences.filter(
|
|
62
|
-
(word, index) => initialSentences.indexOf(word) === index
|
|
63
|
-
);
|
|
64
|
-
setWordsToTranslate((prevWords) => [
|
|
65
|
-
...prevWords,
|
|
66
|
-
...uniqueSentences.filter((word) => !prevWords.includes(word)),
|
|
67
|
-
]);
|
|
68
|
-
}
|
|
69
|
-
}, []);
|
|
70
45
|
|
|
71
46
|
useEffect(() => {
|
|
72
|
-
|
|
73
|
-
handleTranslate(defaultLang, wordsToTranslate);
|
|
74
|
-
// setWordsToTranslate([]); // Clear the list after translating
|
|
75
|
-
}
|
|
47
|
+
handleTranslate(defaultLang, initialSentences);
|
|
76
48
|
}, [defaultLang]);
|
|
77
49
|
|
|
78
50
|
useEffect(() => {
|
|
79
|
-
if (translateData?.
|
|
80
|
-
|
|
51
|
+
if (translateData?.data) {
|
|
81
52
|
const newTranslations = {};
|
|
82
|
-
|
|
53
|
+
initialSentences.map((word, index) => {
|
|
83
54
|
newTranslations[word] = translateData?.data?.result[index];
|
|
55
|
+
console.log(translateData?.data?.result, "result")
|
|
84
56
|
});
|
|
85
57
|
setTranslations((prevTranslations) => ({
|
|
86
58
|
...prevTranslations,
|
|
87
59
|
...newTranslations,
|
|
88
60
|
}));
|
|
89
61
|
}
|
|
90
|
-
}, [translateData?.
|
|
62
|
+
}, [translateData?.data]);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
|
|
66
|
+
if(Object.keys(translations)?.length > 0){
|
|
67
|
+
setIsTranslationsLoading(false)
|
|
68
|
+
}
|
|
69
|
+
}, [translations])
|
|
70
|
+
|
|
91
71
|
|
|
92
72
|
|
|
93
73
|
return {
|
|
@@ -97,8 +77,8 @@ const useTranslation = (initialSentences = []) => {
|
|
|
97
77
|
handleTranslate,
|
|
98
78
|
translations,
|
|
99
79
|
findText,
|
|
100
|
-
|
|
101
|
-
|
|
80
|
+
isTranslationsLoading,
|
|
81
|
+
setIsTranslationsLoading
|
|
102
82
|
};
|
|
103
83
|
};
|
|
104
84
|
|