UNPKG

@selfcommunity/react-core

Version:

React Core Components useful for integrating UI Community components (react-ui).

243 lines (241 loc) • 9.79 kB
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 }; }