@selfcommunity/react-core
Version:
React Core Components useful for integrating UI Community components (react-ui).
243 lines (241 loc) • 9.79 kB
JavaScript
import { __awaiter } from "tslib";
import { useEffect, useMemo, useRef } from 'react';
import { Endpoints, http } from '@selfcommunity/api-services';
import { SCFeatureName, SCCoursePrivacyType, SCCourseJoinStatusType, SCNotificationTopicType, SCNotificationTypologyType, } from '@selfcommunity/types';
import useSCCachingManager from './useSCCachingManager';
import { SCOPE_SC_CORE } from '../constants/Errors';
import { Logger } from '@selfcommunity/utils';
import { useSCPreferences } from '../components/provider/SCPreferencesProvider';
import { SCNotificationMapping } from '../constants/Notification';
import { CONFIGURATIONS_COURSES_ENABLED } from '../constants/Preferences';
import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect';
import PubSub from 'pubsub-js';
/**
:::info
This custom hook is used to manage the courses followed.
:::
:::tip How to use it:
Follow these steps:
```jsx
1. const scUserContext: SCUserContextType = useSCUser();
2. const scJoinedCoursesManager: SCJoinedCoursesManagerType = scUserContext.manager.courses;
3. scJoinedCoursesManager.isJoined(course)
```
:::
*/
export default function useSCJoinedCoursesManager(user) {
const { cache, updateCache, emptyCache, data, setData, loading, setLoading, setUnLoading, isLoading } = useSCCachingManager();
const { preferences, features } = useSCPreferences();
const authUserId = user ? user.id : null;
const coursesEnabled = useMemo(() => preferences &&
features &&
features.includes(SCFeatureName.COURSE) &&
CONFIGURATIONS_COURSES_ENABLED in preferences &&
preferences[CONFIGURATIONS_COURSES_ENABLED].value, [preferences, features]);
const notificationInvitedToJoinCourse = useRef(null);
const notificationAddedToCourse = useRef(null);
/**
* Subscribe to notification types user_follow, user_unfollow
*/
useDeepCompareEffectNoCheck(() => {
notificationInvitedToJoinCourse.current = PubSub.subscribe(`${SCNotificationTopicType.INTERACTION}.${SCNotificationTypologyType.USER_INVITED_TO_JOIN_COURSE}`, notificationSubscriber);
notificationAddedToCourse.current = PubSub.subscribe(`${SCNotificationTopicType.INTERACTION}.${SCNotificationTypologyType.USER_ADDED_TO_COURSE}`, notificationSubscriber);
return () => {
PubSub.unsubscribe(notificationInvitedToJoinCourse.current);
PubSub.unsubscribe(notificationAddedToCourse.current);
};
}, [data]);
/**
* Notification subscriber handler
* @param msg
* @param dataMsg
*/
const notificationSubscriber = (msg, dataMsg) => {
var _a;
if (dataMsg.data.course !== undefined) {
let _status;
switch (SCNotificationMapping[dataMsg.data.activity_type]) {
case SCNotificationTypologyType.USER_INVITED_TO_JOIN_COURSE:
_status = SCCourseJoinStatusType.INVITED;
break;
case SCNotificationTypologyType.USER_ADDED_TO_COURSE:
if (dataMsg.data.notification_obj.course && ((_a = dataMsg.data.notification_obj.course.paywalls) === null || _a === void 0 ? void 0 : _a.length)) {
_status = SCCourseJoinStatusType.PAYMENT_WAITING;
}
else {
_status = SCCourseJoinStatusType.JOINED;
}
break;
}
updateCache([dataMsg.data.course]);
setData((prev) => getDataUpdated(prev, dataMsg.data.course, _status));
}
};
/**
* Memoized refresh all courses
* It makes a single request to the server and retrieves
* all the courses followed by the user in a single solution
* It might be useful for multi-tab sync
*/
const refresh = useMemo(() => () => {
emptyCache();
if (user) {
// Only if user is authenticated
http
.request({
url: Endpoints.GetUserJoinedCourses.url({ id: user.id }),
method: Endpoints.GetUserJoinedCourses.method,
})
.then((res) => {
if (res.status >= 300) {
return Promise.reject(res);
}
const coursesIds = res.data.results.map((c) => c.id);
updateCache(coursesIds);
setData(res.data.results.map((c) => ({ [c.id]: c.join_status })));
return Promise.resolve(res.data);
})
.catch((e) => {
Logger.error(SCOPE_SC_CORE, 'Unable to refresh the authenticated user courses.');
Logger.error(SCOPE_SC_CORE, e);
});
}
}, [data, user, cache]);
/**
* Memoized join Course
* Toggle action
*/
const join = useMemo(() => (course) => __awaiter(this, void 0, void 0, function* () {
setLoading(course.id);
const res = yield http.request({
url: Endpoints.JoinOrAcceptInviteToCourse.url({ id: course.id }),
method: Endpoints.JoinOrAcceptInviteToCourse.method,
});
if (res.status >= 300) {
return Promise.reject(res);
}
updateCache([course.id]);
setData((prev) => getDataUpdated(prev, course.id, course.privacy === SCCoursePrivacyType.PRIVATE && course.join_status !== SCCourseJoinStatusType.INVITED
? SCCourseJoinStatusType.REQUESTED
: SCCourseJoinStatusType.JOINED));
setUnLoading(course.id);
return yield Promise.resolve(res.data);
}), [data, loading, cache]);
/**
* Memoized leave Course
* Toggle action
*/
const leave = useMemo(() => (course) => __awaiter(this, void 0, void 0, function* () {
if (data[course.id] !== SCCourseJoinStatusType.REQUESTED) {
setLoading(course.id);
const res = yield http.request({
url: Endpoints.LeaveOrRemoveCourseRequest.url({ id: course.id }),
method: Endpoints.LeaveOrRemoveCourseRequest.method,
});
if (res.status >= 300) {
return Promise.reject(res);
}
updateCache([course.id]);
setData((prev) => getDataUpdated(prev, course.id, null));
setUnLoading(course.id);
return yield Promise.resolve(res.data);
}
}), [data, loading, cache]);
/**
* Check the authenticated user subscription status to the course
* Update the courses cached
* Update courses subscription statuses
* @param course
*/
const checkCourseJoinedStatus = (course) => {
setLoading(course.id);
return http
.request({
url: Endpoints.GetCourseStatus.url({ id: course.id }),
method: Endpoints.GetCourseStatus.method,
})
.then((res) => {
if (res.status >= 300) {
return Promise.reject(res);
}
setData((prev) => getDataUpdated(prev, course.id, res.data.status));
updateCache([course.id]);
setUnLoading(course.id);
return Promise.resolve(res.data);
})
.catch((e) => {
setUnLoading(course.id);
return Promise.reject(e);
});
};
/**
* Get updated data
* @param data
* @param courseId
* @param joinStatus
*/
const getDataUpdated = (data, courseId, joinStatus) => {
const _index = data.findIndex((k) => parseInt(Object.keys(k)[0]) === courseId);
let _data;
if (_index < 0) {
_data = [...data, ...[{ [courseId]: joinStatus }]];
}
else {
_data = data.map((k, i) => {
if (parseInt(Object.keys(k)[0]) === courseId) {
return { [Object.keys(k)[0]]: joinStatus };
}
return { [Object.keys(k)[0]]: data[i][Object.keys(k)[0]] };
});
}
return _data;
};
/**
* Return current course subscription status if exists,
* otherwise return null
*/
const getCurrentCourseCacheStatus = useMemo(() => (course) => {
const d = data.filter((k) => parseInt(Object.keys(k)[0]) === course.id);
return d.length ? d[0][course.id] : !data.length ? course.join_status : null;
}, [data]);
/**
* Bypass remote check if the course is subscribed
*/
const getJoinStatus = useMemo(() => (course) => {
updateCache([course.id]);
setData((prev) => getDataUpdated(prev, course.id, course.join_status));
return course.join_status;
}, [data, cache]);
/**
* Memoized joinStatus
* If the course is already in cache -> check if the course is in courses,
* otherwise, check if user joined the course
*/
const joinStatus = useMemo(() => (course) => {
// Cache is valid also for anonymous user
if (cache.includes(course.id)) {
return getCurrentCourseCacheStatus(course);
}
if (authUserId && course) {
if ('join_status' in course) {
return getJoinStatus(course);
}
if (!isLoading(course)) {
checkCourseJoinedStatus(course);
}
}
return null;
}, [loading, cache, authUserId, getJoinStatus, getCurrentCourseCacheStatus]);
/**
* Empty cache on logout
*/
useEffect(() => {
if (!authUserId) {
emptyCache();
}
}, [authUserId]);
if (!coursesEnabled || !user) {
return { courses: data, loading, isLoading };
}
return { courses: data, loading, isLoading, join, leave, joinStatus, refresh, emptyCache };
}