@selfcommunity/react-core
Version:
React Core Components useful for integrating UI Community components (react-ui).
263 lines (260 loc) • 13 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useSCUser = exports.SCUserContext = void 0;
const tslib_1 = require("tslib");
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const api_services_1 = require("@selfcommunity/api-services");
const SCContextProvider_1 = require("../SCContextProvider");
const useSCAuth_1 = tslib_1.__importStar(require("../../../hooks/useSCAuth"));
const utils_1 = require("@selfcommunity/utils");
const pubsub_js_1 = tslib_1.__importDefault(require("pubsub-js"));
const Errors_1 = require("../../../constants/Errors");
const use_deep_compare_effect_1 = require("use-deep-compare-effect");
const useSCFollowedCategoriesManager_1 = tslib_1.__importDefault(require("../../../hooks/useSCFollowedCategoriesManager"));
const useSCFollowedManager_1 = tslib_1.__importDefault(require("../../../hooks/useSCFollowedManager"));
const useSCSettingsManager_1 = tslib_1.__importDefault(require("../../../hooks/useSCSettingsManager"));
const useSCFollowersManager_1 = tslib_1.__importDefault(require("../../../hooks/useSCFollowersManager"));
const useSCConnectionsManager_1 = tslib_1.__importDefault(require("../../../hooks/useSCConnectionsManager"));
const types_1 = require("@selfcommunity/types");
const useSCSubscribedIncubatorsManager_1 = tslib_1.__importDefault(require("../../../hooks/useSCSubscribedIncubatorsManager"));
const useSCBlockedUsersManager_1 = tslib_1.__importDefault(require("../../../hooks/useSCBlockedUsersManager"));
const Session = tslib_1.__importStar(require("../../../constants/Session"));
const useSCSubscribedGroupsManager_1 = tslib_1.__importDefault(require("../../../hooks/useSCSubscribedGroupsManager"));
const useSCSubscribedEventsManager_1 = tslib_1.__importDefault(require("../../../hooks/useSCSubscribedEventsManager"));
/**
* SCUserContext (Authentication Context)
*
:::tip Context can be consumed in one of the following ways:
```jsx
1. <SCUserContext.Consumer>{(user, session, error, loading, logout) => (...)}</SCUserContext.Consumer>
```
```jsx
2. const scUserContext: SCUserContextType = useContext(SCUserContext);
```
```jsx
3. const scUserContext: SCUserContextType = useSCUser();
````
:::
*/
exports.SCUserContext = (0, react_1.createContext)({});
/**
* #### Description:
* This component keeps current user logged and session; it is exported as we need to wrap the entire app with it
* @param children
* @return
* ```jsx
* <SCUserContext.Provider value={contextValue}>{!state.loading && children}</SCUserContext.Provider>
* ```
*/
function SCUserProvider({ children }) {
const scContext = (0, react_1.useContext)(SCContextProvider_1.SCContext);
/**
* Manage user session
* Refresh token if necessary
*/
const initialSession = scContext.settings.session;
const { state, dispatch, helpers } = (0, useSCAuth_1.default)(initialSession);
/**
* Helper handle change user
*/
function updateUser(info) {
dispatch({ type: useSCAuth_1.userActionTypes.UPDATE_USER, payload: info });
}
/**
* Managers followed, connections, blocked users, categories, incubators, settings
*/
const settingsManager = (0, useSCSettingsManager_1.default)(state.user);
const followedManager = (0, useSCFollowedManager_1.default)(state.user);
const followersManager = (0, useSCFollowersManager_1.default)(state.user);
const subscribedIncubatorsManager = (0, useSCSubscribedIncubatorsManager_1.default)(state.user);
const connectionsManager = (0, useSCConnectionsManager_1.default)(state.user);
const categoriesManager = (0, useSCFollowedCategoriesManager_1.default)(state.user, updateUser);
const blockedUsersManager = (0, useSCBlockedUsersManager_1.default)(state.user);
const subscribedGroupsManager = (0, useSCSubscribedGroupsManager_1.default)(state.user);
const subscribedEventsManager = (0, useSCSubscribedEventsManager_1.default)(state.user);
/**
* Ref notifications subscribers: BLOCKED_USER, UNBLOCKED_USER
*/
const notificationUserBlockedSubscription = (0, react_1.useRef)(null);
const notificationUserUnBlockedSubscription = (0, react_1.useRef)(null);
/**
* Check if there is a currently active session
* when the provider is mounted for the first time.
* If there is an error, it means there is no session.
*/
(0, use_deep_compare_effect_1.useDeepCompareEffectNoCheck)(() => {
if ((state.session.authToken && state.session.authToken.accessToken) || state.session.type === Session.COOKIE_SESSION) {
dispatch({ type: useSCAuth_1.userActionTypes.LOGIN_LOADING });
api_services_1.UserService.getCurrentUser()
.then((user) => {
dispatch({ type: useSCAuth_1.userActionTypes.LOGIN_SUCCESS, payload: { user } });
})
.catch((error) => {
utils_1.Logger.error(Errors_1.SCOPE_SC_CORE, 'Unable to retrieve the authenticated user.');
dispatch({ type: useSCAuth_1.userActionTypes.LOGIN_FAILURE, payload: { error } });
});
}
else {
dispatch({ type: useSCAuth_1.userActionTypes.LOGIN_FAILURE });
}
}, [state.session]);
/**
* Controls caching of follow categories, users, etc...
* To avoid multi-tab problems (only for client side), on visibility change
* and document is in foreground refresh the cache
*/
(0, react_1.useEffect)(() => {
window.document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
window.document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
/**
* handler handleVisibilityChange for this provider
* Refresh followed categories, users, etc..
*/
function handleVisibilityChange() {
if (!window.document.hidden && state.user) {
settingsManager.refresh && settingsManager.refresh();
refreshCounters();
categoriesManager.refresh && categoriesManager.refresh();
followedManager.refresh && followedManager.refresh();
connectionsManager.refresh && connectionsManager.refresh();
subscribedIncubatorsManager.refresh && subscribedIncubatorsManager.refresh();
blockedUsersManager.refresh && blockedUsersManager.refresh();
subscribedGroupsManager.refresh && subscribedGroupsManager.refresh();
subscribedEventsManager.refresh && subscribedEventsManager.refresh();
}
}
/**
* Subscribes to handle notifications of type BLOCKED_USER, UNBLOCKED_USER.
* When receive this type of notifications, the user.status must be update.
*/
(0, react_1.useEffect)(() => {
notificationUserBlockedSubscription.current = pubsub_js_1.default.subscribe(`${types_1.SCNotificationTopicType.INTERACTION}.${types_1.SCNotificationTypologyType.BLOCKED_USER}`, () => {
dispatch({ type: useSCAuth_1.userActionTypes.UPDATE_USER, payload: { status: types_1.SCUserStatus.BLOCKED } });
});
notificationUserUnBlockedSubscription.current = pubsub_js_1.default.subscribe(`${types_1.SCNotificationTopicType.INTERACTION}.${types_1.SCNotificationTypologyType.UNBLOCKED_USER}`, () => {
dispatch({ type: useSCAuth_1.userActionTypes.UPDATE_USER, payload: { status: types_1.SCUserStatus.APPROVED } });
});
return () => {
pubsub_js_1.default.unsubscribe(notificationUserBlockedSubscription.current);
pubsub_js_1.default.unsubscribe(notificationUserUnBlockedSubscription.current);
};
}, []);
/**
* Handle change unseen interactions counter
*/
function setUnseenInteractionsCounter(counter) {
dispatch({ type: useSCAuth_1.userActionTypes.UPDATE_USER, payload: { unseen_interactions_counter: Math.max(counter, 0) } });
}
/**
* Handle change unseen notification banners counter
*/
function setUnseenNotificationBannersCounter(counter) {
dispatch({ type: useSCAuth_1.userActionTypes.UPDATE_USER, payload: { unseen_notification_banners_counter: Math.max(counter, 0) } });
}
/**
* Helper to refresh the session
*/
function refreshSession() {
return helpers.refreshSession();
}
/**
* Helper to refresh counters
* Use getCurrentUser service since the counters
* have been inserted into the serialized user object
*/
const refreshCounters = (0, react_1.useMemo)(() => () => {
if (state.user) {
return api_services_1.UserService.getCurrentUser()
.then((user) => {
dispatch({
type: useSCAuth_1.userActionTypes.UPDATE_USER,
payload: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ unseen_interactions_counter: user.unseen_interactions_counter, unseen_notification_banners_counter: user.unseen_notification_banners_counter }, (user.followers_counter ? { followers_counter: user.followers_counter } : {})), (user.followings_counter ? { followings_counter: user.followings_counter } : {})), (user.categories_counter ? { categories_counter: user.categories_counter } : {})), (user.discussions_counter ? { discussions_counter: user.discussions_counter } : {})), (user.posts_counter ? { posts_counter: user.posts_counter } : {})), (user.statuses_counter ? { status_counter: user.statuses_counter } : {})), (user.polls_counter ? { polls_counter: user.polls_counter } : {})), (user.connections_counter ? { connections_counter: user.connections_counter } : {})), (user.connection_requests_sent_counter ? { connection_requests_sent_counter: user.connection_requests_sent_counter } : {})), (user.connection_requests_received_counter
? { connection_requests_received_counter: user.connection_requests_received_counter }
: {})),
});
})
.catch((error) => {
utils_1.Logger.error(Errors_1.SCOPE_SC_CORE, `Unable to refresh counters. Error: ${error.response.toString()}`);
});
}
return Promise.reject();
}, [state.user]);
/**
* Call the logout endpoint and then remove the user
* from the state.
*/
function logout() {
helpers.logoutSession();
}
/**
* Make the provider update only when it should.
* We only want to force re-renders if the user, session,
* loading or error states change.
*
* Whenever the `value` passed into a provider changes,
* the whole tree under the provider re-renders, and
* that can be very costly! Even in this case, where
* you only get re-renders when logging in and out
* we want to keep things very performant.
*/
const contextValue = (0, react_1.useMemo)(() => ({
user: state.user,
session: state.session,
loading: state.loading,
error: state.error,
updateUser,
setUnseenInteractionsCounter,
setUnseenNotificationBannersCounter,
refreshCounters,
logout,
refreshSession,
managers: {
settings: settingsManager,
categories: categoriesManager,
followed: followedManager,
followers: followersManager,
connections: connectionsManager,
incubators: subscribedIncubatorsManager,
blockedUsers: blockedUsersManager,
groups: subscribedGroupsManager,
events: subscribedEventsManager,
},
}), [
state,
settingsManager.all,
settingsManager.isLoading,
categoriesManager.loading,
categoriesManager.categories,
followedManager.loading,
followedManager.followed,
followersManager.loading,
followersManager.followers,
connectionsManager.loading,
connectionsManager.connections,
blockedUsersManager.blocked,
subscribedIncubatorsManager.loading,
subscribedIncubatorsManager.incubators,
subscribedGroupsManager.loading,
subscribedGroupsManager.groups,
subscribedEventsManager.loading,
subscribedEventsManager.events,
]);
/**
* We only want to render the underlying app after we
* assert for the presence of a current user.
*/
return (0, jsx_runtime_1.jsx)(exports.SCUserContext.Provider, Object.assign({ value: contextValue }, { children: children }));
}
exports.default = SCUserProvider;
/**
* Let's only export the `useSCUser` hook instead of the context.
* We only want to use the hook directly and never the context component.
*/
function useSCUser() {
return (0, react_1.useContext)(exports.SCUserContext);
}
exports.useSCUser = useSCUser;