@selfcommunity/react-core 0.6.7-alpha.9 → 0.6.7-payments.143

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.
Files changed (58) hide show
  1. package/lib/cjs/components/provider/SCUserProvider/index.js +6 -0
  2. package/lib/cjs/constants/Cache.d.ts +21 -2
  3. package/lib/cjs/constants/Cache.js +29 -4
  4. package/lib/cjs/constants/Preferences.d.ts +13 -0
  5. package/lib/cjs/constants/Preferences.js +24 -1
  6. package/lib/cjs/constants/Routes.d.ts +8 -0
  7. package/lib/cjs/constants/Routes.js +17 -1
  8. package/lib/cjs/hooks/useSCFetchCategories.d.ts +2 -0
  9. package/lib/cjs/hooks/useSCFetchCategories.js +7 -4
  10. package/lib/cjs/hooks/useSCFetchCourse.d.ts +23 -0
  11. package/lib/cjs/hooks/useSCFetchCourse.js +81 -0
  12. package/lib/cjs/hooks/useSCFetchCourses.d.ts +22 -0
  13. package/lib/cjs/hooks/useSCFetchCourses.js +83 -0
  14. package/lib/cjs/hooks/useSCFetchLesson.d.ts +22 -0
  15. package/lib/cjs/hooks/useSCFetchLesson.js +72 -0
  16. package/lib/cjs/hooks/useSCFetchLessonCommentObject.d.ts +23 -0
  17. package/lib/cjs/hooks/useSCFetchLessonCommentObject.js +72 -0
  18. package/lib/cjs/hooks/useSCFetchLessonCommentObjects.d.ts +48 -0
  19. package/lib/cjs/hooks/useSCFetchLessonCommentObjects.js +302 -0
  20. package/lib/cjs/hooks/useSCFetchUserBlockedBy.js +1 -1
  21. package/lib/cjs/hooks/useSCJoinedCoursesManager.d.ts +38 -0
  22. package/lib/cjs/hooks/useSCJoinedCoursesManager.js +277 -0
  23. package/lib/cjs/index.d.ts +7 -2
  24. package/lib/cjs/index.js +12 -2
  25. package/lib/cjs/themes/theme.js +12 -0
  26. package/lib/cjs/types/context.d.ts +36 -1
  27. package/lib/cjs/types/index.d.ts +2 -2
  28. package/lib/cjs/types/theme.d.ts +52 -0
  29. package/lib/esm/components/provider/SCUserProvider/index.js +6 -0
  30. package/lib/esm/constants/Cache.d.ts +21 -2
  31. package/lib/esm/constants/Cache.js +21 -2
  32. package/lib/esm/constants/Preferences.d.ts +13 -0
  33. package/lib/esm/constants/Preferences.js +23 -0
  34. package/lib/esm/constants/Routes.d.ts +8 -0
  35. package/lib/esm/constants/Routes.js +16 -0
  36. package/lib/esm/hooks/useSCFetchCategories.d.ts +2 -0
  37. package/lib/esm/hooks/useSCFetchCategories.js +7 -4
  38. package/lib/esm/hooks/useSCFetchCourse.d.ts +23 -0
  39. package/lib/esm/hooks/useSCFetchCourse.js +78 -0
  40. package/lib/esm/hooks/useSCFetchCourses.d.ts +22 -0
  41. package/lib/esm/hooks/useSCFetchCourses.js +81 -0
  42. package/lib/esm/hooks/useSCFetchLesson.d.ts +22 -0
  43. package/lib/esm/hooks/useSCFetchLesson.js +69 -0
  44. package/lib/esm/hooks/useSCFetchLessonCommentObject.d.ts +23 -0
  45. package/lib/esm/hooks/useSCFetchLessonCommentObject.js +69 -0
  46. package/lib/esm/hooks/useSCFetchLessonCommentObjects.d.ts +48 -0
  47. package/lib/esm/hooks/useSCFetchLessonCommentObjects.js +297 -0
  48. package/lib/esm/hooks/useSCFetchUserBlockedBy.js +1 -1
  49. package/lib/esm/hooks/useSCJoinedCoursesManager.d.ts +38 -0
  50. package/lib/esm/hooks/useSCJoinedCoursesManager.js +273 -0
  51. package/lib/esm/index.d.ts +7 -2
  52. package/lib/esm/index.js +6 -1
  53. package/lib/esm/themes/theme.js +12 -0
  54. package/lib/esm/types/context.d.ts +36 -1
  55. package/lib/esm/types/index.d.ts +2 -2
  56. package/lib/esm/types/theme.d.ts +52 -0
  57. package/lib/umd/react-core.js +1 -1
  58. package/package.json +6 -6
@@ -0,0 +1,297 @@
1
+ import { useEffect, useReducer } from 'react';
2
+ import { SCOPE_SC_CORE } from '../constants/Errors';
3
+ import { SCCommentsOrderBy } from '@selfcommunity/types';
4
+ import { Endpoints, http } from '@selfcommunity/api-services';
5
+ import { CacheStrategies, Logger, LRUCache } from '@selfcommunity/utils';
6
+ import { getLessonCommentCacheKey, getLessonCommentsCacheKey } from '../constants/Cache';
7
+ import { useIsComponentMountedRef } from '../utils/hooks';
8
+ import { getCurrentPage } from '../utils/pagination';
9
+ import useSCFetchLesson from '../hooks/useSCFetchLesson';
10
+ /**
11
+ * @hidden
12
+ * We have complex state logic that involves multiple sub-values,
13
+ * so useReducer is preferable to useState.
14
+ * Define all possible auth action types label
15
+ * Use this to export actions and dispatch an action
16
+ */
17
+ export const commentsObjectActionTypes = {
18
+ LOADING_NEXT: '_loading_next',
19
+ LOADING_PREVIOUS: '_loading_previous',
20
+ DATA_NEXT_LOADED: '_data_next_loaded',
21
+ DATA_PREVIOUS_LOADED: '_data_previous_loaded',
22
+ DATA_RELOAD: '_data_reload',
23
+ DATA_RELOADED: '_data_reloaded',
24
+ DATA_REVALIDATE: '_data_revalidate',
25
+ };
26
+ /**
27
+ * commentsReducer:
28
+ * - manage the state of comments object
29
+ * - update the state base on action type
30
+ * @param state
31
+ * @param action
32
+ */
33
+ function commentsReducer(state, action) {
34
+ switch (action.type) {
35
+ case commentsObjectActionTypes.LOADING_NEXT:
36
+ return Object.assign(Object.assign({}, state), { isLoadingNext: true, isLoadingPrevious: false });
37
+ case commentsObjectActionTypes.LOADING_PREVIOUS:
38
+ return Object.assign(Object.assign({}, state), { isLoadingNext: false, isLoadingPrevious: true });
39
+ case commentsObjectActionTypes.DATA_NEXT_LOADED:
40
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, state), { comments: action.payload.comments, isLoadingNext: false, componentLoaded: true, revalidate: false, revalidateNext: null, revalidatePrevious: null, next: action.payload.next }), (action.payload.page ? { page: action.payload.page } : {})), (action.payload.nextPage ? { nextPage: action.payload.nextPage } : {})), (action.payload.previous ? { previous: action.payload.previous } : {})), (action.payload.previousPage ? { previousPage: action.payload.previousPage } : {})), (action.payload.total ? { total: action.payload.total } : {}));
41
+ case commentsObjectActionTypes.DATA_PREVIOUS_LOADED:
42
+ return Object.assign(Object.assign(Object.assign(Object.assign({}, state), { comments: action.payload.comments, isLoadingPrevious: false, revalidate: false, revalidateNext: null, revalidatePrevious: null, previous: action.payload.previous }), (action.payload.page ? { page: action.payload.page } : {})), (action.payload.previousPage ? { previousPage: action.payload.previousPage } : {}));
43
+ case commentsObjectActionTypes.DATA_RELOAD:
44
+ return Object.assign(Object.assign({}, state), { next: action.payload.next, previousPage: null, nextPage: null, comments: [], total: 0, previous: null, isLoadingNext: true, reload: true });
45
+ case commentsObjectActionTypes.DATA_RELOADED:
46
+ return Object.assign(Object.assign({}, state), { componentLoaded: true, reload: false });
47
+ case commentsObjectActionTypes.DATA_REVALIDATE:
48
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, state), { componentLoaded: true, revalidate: true }), (action.payload.revalidateNext ? { revalidateNext: action.payload.revalidateNext } : {})), (action.payload.revalidatePrevious ? { revalidatePrevious: action.payload.revalidatePrevious } : {})), { reload: false });
49
+ default:
50
+ throw new Error(`Unhandled type: ${action.type}`);
51
+ }
52
+ }
53
+ /**
54
+ * Define initial state
55
+ * @param data
56
+ */
57
+ function stateInitializer(data) {
58
+ const __commentsObjectCacheKey = data.scLesson ? getLessonCommentsCacheKey(data.scLesson.id, data.next) : null;
59
+ let _initState = {
60
+ componentLoaded: false,
61
+ comments: [],
62
+ total: 0,
63
+ next: data.next,
64
+ previous: null,
65
+ isLoadingNext: false,
66
+ isLoadingPrevious: false,
67
+ page: Math.ceil(data.offset / data.pageSize + 1),
68
+ reload: false,
69
+ revalidate: false,
70
+ revalidateNext: null,
71
+ revalidatePrevious: null,
72
+ };
73
+ _initState['nextPage'] = _initState.next ? _initState.page + 1 : null;
74
+ _initState['previousPage'] = _initState.previous ? _initState.page - 1 : null;
75
+ if (__commentsObjectCacheKey && LRUCache.hasKey(__commentsObjectCacheKey) && data.cacheStrategy !== CacheStrategies.NETWORK_ONLY) {
76
+ const _cachedData = LRUCache.get(__commentsObjectCacheKey);
77
+ let page = Math.max(getCurrentPage(_cachedData.next, data.pageSize), 1);
78
+ const nextPage = _cachedData.next ? getCurrentPage(_cachedData.next, data.pageSize) : null;
79
+ const previousPage = _cachedData.previous ? Math.max(getCurrentPage(_cachedData.previous, data.pageSize) - 1, 1) : null;
80
+ return Object.assign(Object.assign({}, _initState), {
81
+ total: _cachedData.count,
82
+ next: _cachedData.next,
83
+ previous: _cachedData.previous,
84
+ comments: _cachedData.results,
85
+ page,
86
+ nextPage,
87
+ previousPage,
88
+ componentLoaded: true,
89
+ });
90
+ }
91
+ return _initState;
92
+ }
93
+ /**
94
+ :::info
95
+ This custom hooks is used to fetch paginated comments for a specific course lesson.
96
+ :::
97
+ * @param props
98
+ */
99
+ export default function useSCFetchLessonCommentObjects(props) {
100
+ // PROPS
101
+ const { id, lessonObject, offset = 0, pageSize = 5, orderBy = SCCommentsOrderBy.ADDED_AT_DESC, parent, onChangePage, cacheStrategy = CacheStrategies.NETWORK_ONLY, } = props;
102
+ // FeedObject
103
+ const { scLesson, setSCLesson } = useSCFetchLesson({
104
+ id: id,
105
+ lesson: lessonObject,
106
+ courseId: lessonObject.course_id,
107
+ sectionId: lessonObject.section_id,
108
+ cacheStrategy,
109
+ });
110
+ /**
111
+ * Get next url
112
+ */
113
+ const getNextUrl = () => {
114
+ const _offset = offset ? `&offset=${offset}` : '';
115
+ const _parent = parent ? `&parent=${parent}` : '';
116
+ const _lessonId = scLesson ? scLesson.id : id;
117
+ const _courseId = scLesson ? scLesson.course_id : lessonObject.course_id;
118
+ const _sectionId = scLesson ? scLesson.section_id : lessonObject.section_id;
119
+ return `${Endpoints.GetCourseLessonComments.url({
120
+ id: _courseId,
121
+ section_id: _sectionId,
122
+ lesson_id: _lessonId,
123
+ })}?limit=${pageSize}&ordering=${orderBy}${_offset}${_parent}`;
124
+ };
125
+ // STATE
126
+ const [state, dispatch] = useReducer(commentsReducer, { scLesson, offset, pageSize, next: getNextUrl(), cacheStrategy }, stateInitializer);
127
+ // REFS
128
+ const isMountedRef = useIsComponentMountedRef();
129
+ /**
130
+ * Get Comments (with cache)
131
+ */
132
+ const revalidate = (url, forward) => {
133
+ return performFetchComments(url, false).then((res) => {
134
+ let _comments;
135
+ let page = getCurrentPage(forward ? res.next : res.previous, pageSize);
136
+ if (forward) {
137
+ let start = state.comments.slice(0, state.comments.length - res.results.length);
138
+ _comments = start.concat(res.results);
139
+ }
140
+ else {
141
+ let start = state.comments.slice(res.results.length, state.comments.length);
142
+ _comments = res.results.concat(start);
143
+ }
144
+ if (isMountedRef.current) {
145
+ dispatch({
146
+ type: forward ? commentsObjectActionTypes.DATA_NEXT_LOADED : commentsObjectActionTypes.DATA_PREVIOUS_LOADED,
147
+ payload: Object.assign(Object.assign({ page, comments: _comments }, (forward ? { next: res.next } : { previous: res.previous })), { total: res.count }),
148
+ });
149
+ }
150
+ });
151
+ };
152
+ /**
153
+ * Get Comments
154
+ */
155
+ const performFetchComments = (url, seekCache = true) => {
156
+ const __commentObjectsCacheKey = getLessonCommentsCacheKey(scLesson.id, url);
157
+ if (seekCache && LRUCache.hasKey(__commentObjectsCacheKey) && cacheStrategy !== CacheStrategies.NETWORK_ONLY) {
158
+ return Promise.resolve(LRUCache.get(__commentObjectsCacheKey));
159
+ }
160
+ return http
161
+ .request({
162
+ url,
163
+ method: Endpoints.GetCourseLessonComments.method,
164
+ })
165
+ .then((res) => {
166
+ if (res.status >= 300) {
167
+ return Promise.reject(res);
168
+ }
169
+ LRUCache.set(__commentObjectsCacheKey, res.data);
170
+ res.data.results.forEach((e) => LRUCache.set(getLessonCommentCacheKey(e.id), e));
171
+ return Promise.resolve(res.data);
172
+ });
173
+ };
174
+ /**
175
+ * Fetch previous comments
176
+ */
177
+ function getPreviousPage() {
178
+ if (scLesson && state.previous && !state.isLoadingPrevious) {
179
+ const _previous = state.previous;
180
+ dispatch({ type: commentsObjectActionTypes.LOADING_PREVIOUS });
181
+ performFetchComments(_previous)
182
+ .then((res) => {
183
+ if (isMountedRef.current) {
184
+ let currentPage = getCurrentPage(_previous, pageSize);
185
+ let previousPage = res.previous ? currentPage - 1 : null;
186
+ dispatch({
187
+ type: commentsObjectActionTypes.DATA_PREVIOUS_LOADED,
188
+ payload: {
189
+ page: currentPage,
190
+ previousPage,
191
+ comments: [...res.results, ...state.comments],
192
+ previous: res.previous,
193
+ },
194
+ });
195
+ onChangePage && onChangePage(currentPage);
196
+ }
197
+ })
198
+ .catch((error) => {
199
+ Logger.error(SCOPE_SC_CORE, error);
200
+ })
201
+ .then(() => {
202
+ if (isMountedRef.current && cacheStrategy === CacheStrategies.STALE_WHILE_REVALIDATE) {
203
+ dispatch({ type: commentsObjectActionTypes.DATA_REVALIDATE, payload: { revalidatePrevious: _previous } });
204
+ }
205
+ });
206
+ }
207
+ }
208
+ /**
209
+ * Fetch next comments
210
+ */
211
+ function getNextPage() {
212
+ if (scLesson && state.next && !state.isLoadingNext) {
213
+ const _next = state.next;
214
+ dispatch({ type: commentsObjectActionTypes.LOADING_NEXT });
215
+ performFetchComments(_next)
216
+ .then((res) => {
217
+ if (isMountedRef.current) {
218
+ let currentPage = getCurrentPage(_next, pageSize);
219
+ let nextPage = res.next ? currentPage + 1 : null;
220
+ dispatch({
221
+ type: commentsObjectActionTypes.DATA_NEXT_LOADED,
222
+ payload: Object.assign({ page: currentPage, nextPage, comments: [...state.comments, ...res.results], next: res.next, total: res.count, componentLoaded: true }, (offset && state.comments.length === 0 ? { previous: res.previous, previousPage: res.previous ? currentPage - 1 : null } : {})),
223
+ });
224
+ onChangePage && onChangePage(currentPage);
225
+ }
226
+ })
227
+ .catch((error) => {
228
+ Logger.error(SCOPE_SC_CORE, error);
229
+ })
230
+ .then(() => {
231
+ if (isMountedRef.current && cacheStrategy === CacheStrategies.STALE_WHILE_REVALIDATE) {
232
+ dispatch({ type: commentsObjectActionTypes.DATA_REVALIDATE, payload: { revalidateNext: _next } });
233
+ }
234
+ });
235
+ }
236
+ }
237
+ /**
238
+ * Reset component status on change orderBy, pageSize, offset
239
+ */
240
+ const reload = () => {
241
+ if (isMountedRef.current && state.componentLoaded && Boolean(scLesson) && !state.isLoadingNext && !state.reload) {
242
+ dispatch({
243
+ type: commentsObjectActionTypes.DATA_RELOAD,
244
+ payload: {
245
+ next: getNextUrl(),
246
+ },
247
+ });
248
+ }
249
+ };
250
+ /**
251
+ * Reload fetch comments
252
+ */
253
+ useEffect(() => {
254
+ if (isMountedRef.current && state.componentLoaded && state.reload && !state.isLoadingNext && !state.isLoadingPrevious) {
255
+ dispatch({
256
+ type: commentsObjectActionTypes.DATA_RELOADED,
257
+ });
258
+ getNextPage();
259
+ }
260
+ }, [state.reload, isMountedRef]);
261
+ /**
262
+ * Revalidate last fetched comments
263
+ */
264
+ useEffect(() => {
265
+ if (isMountedRef.current && state.componentLoaded && Boolean(scLesson) && !state.reload && state.revalidate) {
266
+ revalidate(state.revalidateNext, Boolean(state.revalidateNext));
267
+ }
268
+ }, [state.revalidate, isMountedRef]);
269
+ /**
270
+ * Update lesson comments list
271
+ * @param updatedData
272
+ */
273
+ const updateLessonComments = (updatedData) => {
274
+ if (updatedData) {
275
+ dispatch({
276
+ type: commentsObjectActionTypes.DATA_NEXT_LOADED,
277
+ payload: {
278
+ comments: updatedData,
279
+ next: state.next,
280
+ total: updatedData.length,
281
+ },
282
+ });
283
+ const __commentsCacheKey = getLessonCommentsCacheKey(scLesson.id, state.next);
284
+ LRUCache.set(__commentsCacheKey, updatedData.map((comment) => {
285
+ const __commentCacheKey = getLessonCommentCacheKey(comment.id);
286
+ LRUCache.set(__commentCacheKey, comment);
287
+ return comment.id;
288
+ }));
289
+ }
290
+ };
291
+ return Object.assign(Object.assign({ lessonObject: scLesson, setLessonObject: setSCLesson }, state), { pageSize,
292
+ getNextPage,
293
+ getPreviousPage,
294
+ orderBy,
295
+ reload,
296
+ updateLessonComments });
297
+ }
@@ -69,7 +69,7 @@ export default function useSCFetchUserBlockedBy({ user = null, blockedByUser = n
69
69
  */
70
70
  useEffect(() => {
71
71
  let interval;
72
- if (scUserContext.user && blockedBy !== null && sync) {
72
+ if (scUserContext.user && scUserContext.user.id !== (user === null || user === void 0 ? void 0 : user.id) && blockedBy !== null && sync) {
73
73
  interval = setInterval(() => {
74
74
  fetchUserBlockedBy(user, false);
75
75
  }, 5000);
@@ -0,0 +1,38 @@
1
+ import { SCCourseType, SCUserType } from '@selfcommunity/types';
2
+ /**
3
+ :::info
4
+ This custom hook is used to manage the courses followed.
5
+ :::
6
+
7
+ :::tip How to use it:
8
+ Follow these steps:
9
+ ```jsx
10
+ 1. const scUserContext: SCUserContextType = useSCUser();
11
+ 2. const scJoinedCoursesManager: SCJoinedCoursesManagerType = scUserContext.manager.courses;
12
+ 3. scJoinedCoursesManager.isJoined(course)
13
+ ```
14
+ :::
15
+ */
16
+ export default function useSCJoinedCoursesManager(user?: SCUserType): {
17
+ courses: any[];
18
+ loading: any[];
19
+ isLoading: (v: number | {
20
+ id: number;
21
+ }) => boolean;
22
+ join?: undefined;
23
+ leave?: undefined;
24
+ joinStatus?: undefined;
25
+ refresh?: undefined;
26
+ emptyCache?: undefined;
27
+ } | {
28
+ courses: any[];
29
+ loading: any[];
30
+ isLoading: (v: number | {
31
+ id: number;
32
+ }) => boolean;
33
+ join: (course: SCCourseType, userId?: number) => Promise<any>;
34
+ leave: (course: SCCourseType) => Promise<any>;
35
+ joinStatus: (course: SCCourseType) => string;
36
+ refresh: () => void;
37
+ emptyCache: () => void;
38
+ };
@@ -0,0 +1,273 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ import { Endpoints, http } from '@selfcommunity/api-services';
3
+ import { SCFeatureName, SCCoursePrivacyType, SCCourseJoinStatusType, SCNotificationTopicType, SCNotificationTypologyType, } from '@selfcommunity/types';
4
+ import useSCCachingManager from './useSCCachingManager';
5
+ import { SCOPE_SC_CORE } from '../constants/Errors';
6
+ import { Logger } from '@selfcommunity/utils';
7
+ import { useSCPreferences } from '../components/provider/SCPreferencesProvider';
8
+ import { SCNotificationMapping } from '../constants/Notification';
9
+ import { CONFIGURATIONS_COURSES_ENABLED } from '../constants/Preferences';
10
+ import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect';
11
+ import PubSub from 'pubsub-js';
12
+ /**
13
+ :::info
14
+ This custom hook is used to manage the courses followed.
15
+ :::
16
+
17
+ :::tip How to use it:
18
+ Follow these steps:
19
+ ```jsx
20
+ 1. const scUserContext: SCUserContextType = useSCUser();
21
+ 2. const scJoinedCoursesManager: SCJoinedCoursesManagerType = scUserContext.manager.courses;
22
+ 3. scJoinedCoursesManager.isJoined(course)
23
+ ```
24
+ :::
25
+ */
26
+ export default function useSCJoinedCoursesManager(user) {
27
+ const { cache, updateCache, emptyCache, data, setData, loading, setLoading, setUnLoading, isLoading } = useSCCachingManager();
28
+ const { preferences, features } = useSCPreferences();
29
+ const authUserId = user ? user.id : null;
30
+ const coursesEnabled = useMemo(() => preferences &&
31
+ features &&
32
+ features.includes(SCFeatureName.TAGGING) &&
33
+ features.includes(SCFeatureName.COURSE) &&
34
+ CONFIGURATIONS_COURSES_ENABLED in preferences &&
35
+ preferences[CONFIGURATIONS_COURSES_ENABLED].value, [preferences, features]);
36
+ const notificationInvitedToJoinCourse = useRef(null);
37
+ const notificationRequestedToJoinCourse = useRef(null);
38
+ const notificationAcceptedToJoinCourse = useRef(null);
39
+ const notificationAddedToCourse = useRef(null);
40
+ /**
41
+ * Subscribe to notification types user_follow, user_unfollow
42
+ */
43
+ useDeepCompareEffectNoCheck(() => {
44
+ notificationInvitedToJoinCourse.current = PubSub.subscribe(`${SCNotificationTopicType.INTERACTION}.${SCNotificationTypologyType.USER_INVITED_TO_JOIN_COURSE}`, notificationSubscriber);
45
+ notificationRequestedToJoinCourse.current = PubSub.subscribe(`${SCNotificationTopicType.INTERACTION}.${SCNotificationTypologyType.USER_REQUESTED_TO_JOIN_COURSE}`, notificationSubscriber);
46
+ notificationAcceptedToJoinCourse.current = PubSub.subscribe(`${SCNotificationTopicType.INTERACTION}.${SCNotificationTypologyType.USER_ACCEPTED_TO_JOIN_COURSE}`, notificationSubscriber);
47
+ notificationAddedToCourse.current = PubSub.subscribe(`${SCNotificationTopicType.INTERACTION}.${SCNotificationTypologyType.USER_ADDED_TO_COURSE}`, notificationSubscriber);
48
+ return () => {
49
+ PubSub.unsubscribe(notificationInvitedToJoinCourse.current);
50
+ PubSub.unsubscribe(notificationRequestedToJoinCourse.current);
51
+ PubSub.unsubscribe(notificationAcceptedToJoinCourse.current);
52
+ PubSub.unsubscribe(notificationAddedToCourse.current);
53
+ };
54
+ }, [data]);
55
+ /**
56
+ * Notification subscriber handler
57
+ * @param msg
58
+ * @param dataMsg
59
+ */
60
+ const notificationSubscriber = (msg, dataMsg) => {
61
+ if (dataMsg.data.course !== undefined) {
62
+ let _status;
63
+ switch (SCNotificationMapping[dataMsg.data.activity_type]) {
64
+ case SCNotificationTypologyType.USER_INVITED_TO_JOIN_COURSE:
65
+ _status = SCCourseJoinStatusType.INVITED;
66
+ break;
67
+ case SCNotificationTypologyType.USER_REQUESTED_TO_JOIN_COURSE:
68
+ _status = SCCourseJoinStatusType.REQUESTED;
69
+ break;
70
+ case SCNotificationTypologyType.USER_ACCEPTED_TO_JOIN_COURSE:
71
+ _status = SCCourseJoinStatusType.JOINED;
72
+ break;
73
+ case SCNotificationTypologyType.USER_ADDED_TO_COURSE:
74
+ _status = SCCourseJoinStatusType.JOINED;
75
+ break;
76
+ }
77
+ updateCache([dataMsg.data.course.id]);
78
+ setData((prev) => getDataUpdated(prev, dataMsg.data.course.id, _status));
79
+ }
80
+ };
81
+ /**
82
+ * Memoized refresh all courses
83
+ * It makes a single request to the server and retrieves
84
+ * all the courses followed by the user in a single solution
85
+ * It might be useful for multi-tab sync
86
+ */
87
+ const refresh = useMemo(() => () => {
88
+ emptyCache();
89
+ if (user) {
90
+ // Only if user is authenticated
91
+ http
92
+ .request({
93
+ url: Endpoints.GetJoinedCourses.url(),
94
+ method: Endpoints.GetJoinedCourses.method,
95
+ })
96
+ .then((res) => {
97
+ if (res.status >= 300) {
98
+ return Promise.reject(res);
99
+ }
100
+ const coursesIds = res.data.results.map((c) => c.id);
101
+ updateCache(coursesIds);
102
+ setData(res.data.results.map((c) => ({ [c.id]: c.join_status })));
103
+ return Promise.resolve(res.data);
104
+ })
105
+ .catch((e) => {
106
+ Logger.error(SCOPE_SC_CORE, 'Unable to refresh the authenticated user courses.');
107
+ Logger.error(SCOPE_SC_CORE, e);
108
+ });
109
+ }
110
+ }, [data, user, cache]);
111
+ /**
112
+ * Memoized join Course
113
+ * Toggle action
114
+ */
115
+ const join = useMemo(() => (course, userId) => {
116
+ setLoading(course.id);
117
+ if (userId) {
118
+ return http
119
+ .request({
120
+ url: Endpoints.InviteOrAcceptUsersToCourse.url({ id: course.id }),
121
+ method: Endpoints.InviteOrAcceptUsersToCourse.method,
122
+ data: { users: [userId] },
123
+ })
124
+ .then((res) => {
125
+ if (res.status >= 300) {
126
+ return Promise.reject(res);
127
+ }
128
+ updateCache([course.id]);
129
+ setData((prev) => getDataUpdated(prev, course.id, SCCourseJoinStatusType.JOINED));
130
+ setUnLoading(course.id);
131
+ return Promise.resolve(res.data);
132
+ });
133
+ }
134
+ else {
135
+ return http
136
+ .request({
137
+ url: Endpoints.JoinOrAcceptInviteToCourse.url({ id: course.id }),
138
+ method: Endpoints.JoinOrAcceptInviteToCourse.method,
139
+ })
140
+ .then((res) => {
141
+ if (res.status >= 300) {
142
+ return Promise.reject(res);
143
+ }
144
+ updateCache([course.id]);
145
+ setData((prev) => getDataUpdated(prev, course.id, course.privacy === SCCoursePrivacyType.PRIVATE && course.join_status !== SCCourseJoinStatusType.INVITED
146
+ ? SCCourseJoinStatusType.REQUESTED
147
+ : SCCourseJoinStatusType.JOINED));
148
+ setUnLoading(course.id);
149
+ return Promise.resolve(res.data);
150
+ });
151
+ }
152
+ }, [data, loading, cache]);
153
+ /**
154
+ * Memoized leave Course
155
+ * Toggle action
156
+ */
157
+ const leave = useMemo(() => (course) => {
158
+ if (course.join_status !== SCCourseJoinStatusType.REQUESTED) {
159
+ setLoading(course.id);
160
+ return http
161
+ .request({
162
+ url: Endpoints.LeaveOrRemoveCourseRequest.url({ id: course.id }),
163
+ method: Endpoints.LeaveOrRemoveCourseRequest.method,
164
+ })
165
+ .then((res) => {
166
+ if (res.status >= 300) {
167
+ return Promise.reject(res);
168
+ }
169
+ updateCache([course.id]);
170
+ setData((prev) => getDataUpdated(prev, course.id, null));
171
+ setUnLoading(course.id);
172
+ return Promise.resolve(res.data);
173
+ });
174
+ }
175
+ }, [data, loading, cache]);
176
+ /**
177
+ * Check the authenticated user subscription status to the course
178
+ * Update the courses cached
179
+ * Update courses subscription statuses
180
+ * @param course
181
+ */
182
+ const checkCourseJoinedStatus = (course) => {
183
+ setLoading(course.id);
184
+ return http
185
+ .request({
186
+ url: Endpoints.GetCourseStatus.url({ id: course.id }),
187
+ method: Endpoints.GetCourseStatus.method,
188
+ })
189
+ .then((res) => {
190
+ if (res.status >= 300) {
191
+ return Promise.reject(res);
192
+ }
193
+ setData((prev) => getDataUpdated(prev, course.id, res.data.status));
194
+ updateCache([course.id]);
195
+ setUnLoading(course.id);
196
+ return Promise.resolve(res.data);
197
+ })
198
+ .catch((e) => {
199
+ setUnLoading(course.id);
200
+ return Promise.reject(e);
201
+ });
202
+ };
203
+ /**
204
+ * Get updated data
205
+ * @param data
206
+ * @param courseId
207
+ * @param joinStatus
208
+ */
209
+ const getDataUpdated = (data, courseId, joinStatus) => {
210
+ const _index = data.findIndex((k) => parseInt(Object.keys(k)[0]) === courseId);
211
+ let _data;
212
+ if (_index < 0) {
213
+ _data = [...data, ...[{ [courseId]: joinStatus }]];
214
+ }
215
+ else {
216
+ _data = data.map((k, i) => {
217
+ if (parseInt(Object.keys(k)[0]) === courseId) {
218
+ return { [Object.keys(k)[0]]: joinStatus };
219
+ }
220
+ return { [Object.keys(k)[0]]: data[i][Object.keys(k)[0]] };
221
+ });
222
+ }
223
+ return _data;
224
+ };
225
+ /**
226
+ * Return current course subscription status if exists,
227
+ * otherwise return null
228
+ */
229
+ const getCurrentCourseCacheStatus = useMemo(() => (course) => {
230
+ const d = data.filter((k) => parseInt(Object.keys(k)[0]) === course.id);
231
+ return d.length ? d[0][course.id] : !data.length ? course.join_status : null;
232
+ }, [data]);
233
+ /**
234
+ * Bypass remote check if the course is subscribed
235
+ */
236
+ const getJoinStatus = useMemo(() => (course) => {
237
+ updateCache([course.id]);
238
+ setData((prev) => getDataUpdated(prev, course.id, course.join_status));
239
+ return course.join_status;
240
+ }, [data, cache]);
241
+ /**
242
+ * Memoized joinStatus
243
+ * If the course is already in cache -> check if the course is in courses,
244
+ * otherwise, check if user joined the course
245
+ */
246
+ const joinStatus = useMemo(() => (course) => {
247
+ // Cache is valid also for anonymous user
248
+ if (cache.includes(course.id)) {
249
+ return getCurrentCourseCacheStatus(course);
250
+ }
251
+ if (authUserId) {
252
+ if ('join_status' in course) {
253
+ return getJoinStatus(course);
254
+ }
255
+ if (!isLoading(course)) {
256
+ checkCourseJoinedStatus(course);
257
+ }
258
+ }
259
+ return null;
260
+ }, [loading, cache, authUserId]);
261
+ /**
262
+ * Empty cache on logout
263
+ */
264
+ useEffect(() => {
265
+ if (!authUserId) {
266
+ emptyCache();
267
+ }
268
+ }, [authUserId]);
269
+ if (!coursesEnabled || !user) {
270
+ return { courses: data, loading, isLoading };
271
+ }
272
+ return { courses: data, loading, isLoading, join, leave, joinStatus, refresh, emptyCache };
273
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Types
3
3
  */
4
- import { SCUserContextType, SCFollowedCategoriesManagerType, SCContextProviderType, SCContextType, SCSettingsType, SCSessionType, SCFollowedManagerType, SCFollowersManagerType, SCSettingsManagerType, SCConnectionsManagerType, SCSubscribedIncubatorsManagerType, SCLocaleType, SCNotificationContextType, SCPreferencesContextType, SCThemeContextType, SCRoutingContextType, SCLocaleContextType, SCAlertMessagesContextType, SCThemeAvatarVariableType, SCThemeCategoryIconVariableType, SCThemeCategoryVariableType, SCThemeVariablesType, SCThemeType, SCSubscribedGroupsManagerType, SCSubscribedEventsManagerType } from './types';
4
+ import { SCUserContextType, SCFollowedCategoriesManagerType, SCContextProviderType, SCContextType, SCSettingsType, SCSessionType, SCFollowedManagerType, SCFollowersManagerType, SCSettingsManagerType, SCConnectionsManagerType, SCSubscribedIncubatorsManagerType, SCLocaleType, SCNotificationContextType, SCPreferencesContextType, SCThemeContextType, SCRoutingContextType, SCLocaleContextType, SCAlertMessagesContextType, SCThemeAvatarVariableType, SCThemeCategoryIconVariableType, SCThemeCategoryVariableType, SCThemeVariablesType, SCThemeType, SCSubscribedGroupsManagerType, SCSubscribedEventsManagerType, SCJoinedCoursesManagerType } from './types';
5
5
  /**
6
6
  * ContextProvider component
7
7
  */
@@ -47,6 +47,8 @@ import useSCFetchVote from './hooks/useSCFetchVote';
47
47
  import useSCFetchFeedObject from './hooks/useSCFetchFeedObject';
48
48
  import useSCFetchCommentObject from './hooks/useSCFetchCommentObject';
49
49
  import useSCFetchCommentObjects from './hooks/useSCFetchCommentObjects';
50
+ import useSCFetchLessonCommentObject from './hooks/useSCFetchLessonCommentObject';
51
+ import useSCFetchLessonCommentObjects from './hooks/useSCFetchLessonCommentObjects';
50
52
  import useSCFetchCustomAdv from './hooks/useSCFetchCustomAdv';
51
53
  import useSCFetchTag from './hooks/useSCFetchTag';
52
54
  import useSCFetchAddressingTagList from './hooks/useSCFetchAddressingTagList';
@@ -66,6 +68,9 @@ import useSCFetchEvent from './hooks/useSCFetchEvent';
66
68
  import useSCFetchEvents from './hooks/useSCFetchEvents';
67
69
  import useSCFetchLiveStream from './hooks/useSCFetchLiveStream';
68
70
  import useSCGoogleApiLoader from './hooks/useSCGoogleApiLoader';
71
+ import useSCFetchCourse from './hooks/useSCFetchCourse';
72
+ import useSCFetchCourses from './hooks/useSCFetchCourses';
73
+ import useSCFetchLesson from './hooks/useSCFetchLesson';
69
74
  /**
70
75
  * Routing component
71
76
  */
@@ -88,4 +93,4 @@ import * as Preferences from './constants/Preferences';
88
93
  /**
89
94
  * List all exports
90
95
  */
91
- export { SCUserContextType, SCFollowedCategoriesManagerType, SCContextProviderType, SCContextType, SCSettingsType, SCSessionType, SCSettingsManagerType, SCFollowedManagerType, SCFollowersManagerType, SCConnectionsManagerType, SCSubscribedIncubatorsManagerType, SCLocaleType, SCNotificationContextType, SCPreferencesContextType, SCThemeContextType, SCRoutingContextType, SCLocaleContextType, SCAlertMessagesContextType, SCThemeAvatarVariableType, SCThemeCategoryIconVariableType, SCThemeCategoryVariableType, SCThemeVariablesType, SCThemeType, SCSubscribedGroupsManagerType, SCSubscribedEventsManagerType, SCContext, SCUserContext, SCThemeContext, SCRoutingContext, SCLocaleContext, SCPreferencesContext, useSCContext, SCContextProvider, SCUserProvider, useSCUser, useSCPreferences, SCThemeProvider, useSCTheme, withSCTheme, getTheme, SCRoutingProvider, useSCRouting, SCLocaleProvider, useSCLocale, withSCLocale, SCPreferencesProvider, SCPreferences, SCFeatures, SCNotification, SCNotificationProvider, SCNotificationContext, useSCNotification, SCAlertMessagesProvider, SCAlertMessagesContext, useSCAlertMessages, Link, SCRoutes, SCCache, UserUtils, getEventStatus, Locale, Preferences, useSCFetchUser, useSCFetchUserProviders, useSCFetchVote, useSCFetchFeedObject, useSCFetchCommentObject, useSCFetchCommentObjects, useSCFetchCustomAdv, useSCFetchTag, useSCFetchAddressingTagList, useSCFetchCategory, useSCFetchCategories, useSCFetchIncubator, useSCMediaClick, useSCFetchContributors, useSCFetchFeed, useIsComponentMountedRef, usePreviousValue, useIsomorphicLayoutEffect, useEffectOnce, useNoInitialEffect, usePageVisibility, useSCFetchPrivateMessageSnippets, useSCFetchBroadcastMessages, useSCFetchUserBlockedBy, useSCUserIsBlocked, useSCFetchGroup, useSCFetchGroups, useSCFetchEvent, useSCFetchEvents, useSCFetchLiveStream, useSCGoogleApiLoader, };
96
+ export { SCUserContextType, SCFollowedCategoriesManagerType, SCContextProviderType, SCContextType, SCSettingsType, SCSessionType, SCSettingsManagerType, SCFollowedManagerType, SCFollowersManagerType, SCConnectionsManagerType, SCSubscribedIncubatorsManagerType, SCLocaleType, SCNotificationContextType, SCPreferencesContextType, SCThemeContextType, SCRoutingContextType, SCLocaleContextType, SCAlertMessagesContextType, SCThemeAvatarVariableType, SCThemeCategoryIconVariableType, SCThemeCategoryVariableType, SCThemeVariablesType, SCThemeType, SCSubscribedGroupsManagerType, SCSubscribedEventsManagerType, SCJoinedCoursesManagerType, SCContext, SCUserContext, SCThemeContext, SCRoutingContext, SCLocaleContext, SCPreferencesContext, useSCContext, SCContextProvider, SCUserProvider, useSCUser, useSCPreferences, SCThemeProvider, useSCTheme, withSCTheme, getTheme, SCRoutingProvider, useSCRouting, SCLocaleProvider, useSCLocale, withSCLocale, SCPreferencesProvider, SCPreferences, SCFeatures, SCNotification, SCNotificationProvider, SCNotificationContext, useSCNotification, SCAlertMessagesProvider, SCAlertMessagesContext, useSCAlertMessages, Link, SCRoutes, SCCache, UserUtils, getEventStatus, Locale, Preferences, useSCFetchUser, useSCFetchUserProviders, useSCFetchVote, useSCFetchFeedObject, useSCFetchCommentObject, useSCFetchCommentObjects, useSCFetchLessonCommentObject, useSCFetchLessonCommentObjects, useSCFetchCustomAdv, useSCFetchTag, useSCFetchAddressingTagList, useSCFetchCategory, useSCFetchCategories, useSCFetchIncubator, useSCMediaClick, useSCFetchContributors, useSCFetchFeed, useIsComponentMountedRef, usePreviousValue, useIsomorphicLayoutEffect, useEffectOnce, useNoInitialEffect, usePageVisibility, useSCFetchPrivateMessageSnippets, useSCFetchBroadcastMessages, useSCFetchUserBlockedBy, useSCUserIsBlocked, useSCFetchGroup, useSCFetchGroups, useSCFetchEvent, useSCFetchEvents, useSCFetchLiveStream, useSCGoogleApiLoader, useSCFetchCourse, useSCFetchCourses, useSCFetchLesson, };