Skip to content

Commit

Permalink
feat: converted notification redux structure to context API
Browse files Browse the repository at this point in the history
  • Loading branch information
sundasnoreen12 committed Oct 1, 2024
1 parent bb0e175 commit 8967d0a
Show file tree
Hide file tree
Showing 33 changed files with 2,138 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Factory.define('notificationsCount')
grades: 10,
authoring: 5,
})
.attr('showNotificationsTray', true);
.attr('showNotificationsTray', true)
.attr('isNewNotificationViewEnabled', false);

Factory.define('notification')
.sequence('id')
Expand Down
2 changes: 2 additions & 0 deletions src/Notifications/data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const selectSelectedAppNotificationIds = (appName) => state => state.noti

export const selectShowNotificationTray = state => state.notifications.showNotificationsTray;

export const selectIsNewNotificationViewEnabled = state => state.notifications.isNewNotificationViewEnabled;

export const selectNotifications = state => state.notifications.notifications;

export const selectNotificationsByIds = (appName) => createSelector(
Expand Down
3 changes: 3 additions & 0 deletions src/Notifications/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const initialState = {
showNotificationsTray: false,
pagination: {},
trayOpened: false,
isNewNotificationViewEnabled: false,
};
const slice = createSlice({
name: 'notifications',
Expand Down Expand Up @@ -54,13 +55,15 @@ const slice = createSlice({
fetchNotificationsCountSuccess: (state, { payload }) => {
const {
countByAppName, appIds, apps, count, showNotificationsTray, notificationExpiryDays,
isNewNotificationViewEnabled,
} = payload;
state.tabsCount = { count, ...countByAppName };
state.appsId = appIds;
state.apps = apps;
state.showNotificationsTray = showNotificationsTray;
state.notificationStatus = RequestStatus.SUCCESSFUL;
state.notificationExpiryDays = notificationExpiryDays;
state.isNewNotificationViewEnabled = isNewNotificationViewEnabled;
},
markAllNotificationsAsReadSuccess: (state) => {
const updatedNotifications = Object.fromEntries(
Expand Down
4 changes: 2 additions & 2 deletions src/Notifications/data/thunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import {
} from './api';

const normalizeNotificationCounts = ({
countByAppName, count, showNotificationsTray, notificationExpiryDays,
countByAppName, count, showNotificationsTray, notificationExpiryDays, isNewNotificationViewEnabled,
}) => {
const appIds = Object.keys(countByAppName);
const apps = appIds.reduce((acc, appId) => { acc[appId] = []; return acc; }, {});
return {
countByAppName, appIds, apps, count, showNotificationsTray, notificationExpiryDays,
countByAppName, appIds, apps, count, showNotificationsTray, notificationExpiryDays, isNewNotificationViewEnabled,
};
};

Expand Down
17 changes: 17 additions & 0 deletions src/common/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { RequestStatus } from '../new-notifications/data/constants';

export const initialState = {
notificationStatus: RequestStatus.IDLE,
notificationListStatus: RequestStatus.IDLE,
appName: 'discussion',
appsId: [],
apps: {},
notifications: {},
tabsCount: {},
showNotificationsTray: false,
pagination: {},
trayOpened: false,
};

export const HeaderContext = React.createContext(initialState);
7 changes: 6 additions & 1 deletion src/learning-header/AuthenticatedUserDropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Dropdown, Badge } from '@openedx/paragon';
import messages from './messages';
import Notifications from '../Notifications';
import UserMenuItem from '../common/UserMenuItem';
import { selectShowNotificationTray } from '../Notifications/data/selectors';
import { selectShowNotificationTray, selectIsNewNotificationViewEnabled } from '../Notifications/data/selectors';
import { fetchAppsNotificationCount } from '../Notifications/data/thunks';

const AuthenticatedUserDropdown = (props) => {
Expand All @@ -25,6 +25,7 @@ const AuthenticatedUserDropdown = (props) => {
} = props;
const dispatch = useDispatch();
const showNotificationsTray = useSelector(selectShowNotificationTray);
const isNewNotificationViewEnabled = useSelector(selectIsNewNotificationViewEnabled);

useEffect(() => {
dispatch(fetchAppsNotificationCount());
Expand Down Expand Up @@ -70,6 +71,10 @@ const AuthenticatedUserDropdown = (props) => {
careersMenuItem = '';
}

if (isNewNotificationViewEnabled) {
return null;
}

return (
<>
<a className="text-gray-700" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
Expand Down
22 changes: 16 additions & 6 deletions src/learning-header/LearningHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AppContext, AppProvider } from '@edx/frontend-platform/react';
import classNames from 'classnames';
import AnonymousUserMenu from './AnonymousUserMenu';
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
import NewAuthenticatedUserDropdown from './New-AuthenticatedUserDropdown';
import messages from './messages';
import lightning from '../lightning';
import store from '../store';
Expand Down Expand Up @@ -89,12 +90,21 @@ const LearningHeader = ({
<span className="d-block m-0 font-weight-bold course-title">{courseTitle}</span>
</div>
{showUserDropdown && authenticatedUser && (
<AuthenticatedUserDropdown
enterpriseLearnerPortalLink={enterpriseLearnerPortalLink}
username={authenticatedUser.username}
name={authenticatedUser.name}
email={authenticatedUser.email}
/>
<>
<AuthenticatedUserDropdown
enterpriseLearnerPortalLink={enterpriseLearnerPortalLink}
username={authenticatedUser.username}
name={authenticatedUser.name}
email={authenticatedUser.email}
/>
<NewAuthenticatedUserDropdown
enterpriseLearnerPortalLink={enterpriseLearnerPortalLink}
username={authenticatedUser.username}
name={authenticatedUser.name}
email={authenticatedUser.email}
/>
</>

)}
{showUserDropdown && !authenticatedUser && (
<AnonymousUserMenu />
Expand Down
137 changes: 137 additions & 0 deletions src/learning-header/New-AuthenticatedUserDropdown.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';

import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Dropdown, Badge } from '@openedx/paragon';

import messages from './messages';
import Notifications from '../new-notifications';
import UserMenuItem from '../common/UserMenuItem';
import { useNotification } from '../new-notifications/data/hook';

const AuthenticatedUserDropdown = (props) => {
const {
intl,
enterpriseLearnerPortalLink,
username,
name,
email,
} = props;
const [showTray, setShowTray] = useState();
const [isNewNotificationView, setIsNewNotificationView] = useState(false);
const [notificationAppData, setNotificationAppData] = useState();
const { fetchAppsNotificationCount } = useNotification();

const fetchNotifications = useCallback(async () => {
const data = await fetchAppsNotificationCount();
const { showNotificationsTray, isNewNotificationViewEnabled } = data;

setShowTray(showNotificationsTray);
setIsNewNotificationView(isNewNotificationViewEnabled);
setNotificationAppData(data);
}, [fetchAppsNotificationCount]);

useEffect(() => {
fetchNotifications();

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [window.location.href]);

let dashboardMenuItem = (
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}>
{intl.formatMessage(messages.dashboard)}
</Dropdown.Item>
);

let careersMenuItem = (
<Dropdown.Item href="https://careers.edx.org/">
{intl.formatMessage(messages.career)}
<Badge className="px-2 mx-2" variant="warning">
{intl.formatMessage(messages.newAlert)}
</Badge>
</Dropdown.Item>
);

const userMenuItem = (name || email) ? (
<Dropdown.Item
key="user-info"
data-testid="user-item"
className="user-info__menu-item"
>
<UserMenuItem
name={name}
email={email}
/>
</Dropdown.Item>
) : null;

if (enterpriseLearnerPortalLink && Object.keys(enterpriseLearnerPortalLink).length > 0) {
dashboardMenuItem = (
<Dropdown.Item
href={enterpriseLearnerPortalLink.href}
>
{enterpriseLearnerPortalLink.content}
</Dropdown.Item>
);
careersMenuItem = '';
}

if (!isNewNotificationView) {
return null;
}

return (
<>
<a className="text-gray-700" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
{showTray && <Notifications notificationAppData={notificationAppData} />}
<Dropdown className="user-dropdown ml-3">
<Dropdown.Toggle variant="outline-primary" id="user-dropdown">
<FontAwesomeIcon icon={faUserCircle} size="lg" />
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right zIndex-2">
{userMenuItem}
{dashboardMenuItem}
{careersMenuItem}
<Dropdown.Item href={`${getConfig().ACCOUNT_PROFILE_URL}/u/${username}`}>
{intl.formatMessage(messages.profile)}
</Dropdown.Item>
<Dropdown.Item href={getConfig().ACCOUNT_SETTINGS_URL}>
{intl.formatMessage(messages.account)}
</Dropdown.Item>
{!enterpriseLearnerPortalLink && getConfig().ORDER_HISTORY_URL && (
// Users should only see Order History if they do not have an available
// learner portal, because an available learner portal currently means
// that they access content via B2B Subscriptions, in which context an "order"
// is not relevant.
<Dropdown.Item href={getConfig().ORDER_HISTORY_URL}>
{intl.formatMessage(messages.orderHistory)}
</Dropdown.Item>
)}
<Dropdown.Item href={getConfig().LOGOUT_URL}>
{intl.formatMessage(messages.signOut)}
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</>
);
};

AuthenticatedUserDropdown.propTypes = {
enterpriseLearnerPortalLink: PropTypes.string,
intl: intlShape.isRequired,
username: PropTypes.string.isRequired,
name: PropTypes.string,
email: PropTypes.string,
};

AuthenticatedUserDropdown.defaultProps = {
enterpriseLearnerPortalLink: '',
name: '',
email: '',
};

export default injectIntl(AuthenticatedUserDropdown);
42 changes: 42 additions & 0 deletions src/new-notifications/NotificationEmptySection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { useContext } from 'react';

import { useIntl } from '@edx/frontend-platform/i18n';
import { Icon, IconButton } from '@openedx/paragon';
import { NotificationsNone } from '@openedx/paragon/icons';

import NotificationContext from './context';
import messages from './messages';

const EmptyNotifications = () => {
const intl = useIntl();
const { popoverHeaderRef, notificationRef } = useContext(NotificationContext);

return (
<div
className="d-flex flex-column justify-content-center align-items-center"
data-testid="notifications-empty-list"
style={{ height: `${notificationRef.current.clientHeight - popoverHeaderRef.current.clientHeight}px` }}
>
<IconButton
isActive
alt={intl.formatMessage(messages.notificationBellIconAltMessage)}
src={NotificationsNone}
iconAs={Icon}
variant="light"
iconClassNames="text-primary-500"
className="ml-4 mr-1 notification-button notification-lg-bell-icon pl-2"
data-testid="notification-bell-icon"
/>
<div className="mx-auto mt-3.5 mb-3 font-size-22 notification-end-title line-height-24">
{intl.formatMessage(messages.noNotificationsYetMessage)}
</div>
<div className="d-flex flex-row mx-auto text-gray-500">
<span className="font-size-14 line-height-normal">
{intl.formatMessage(messages.noNotificationHelpMessage)}
</span>
</div>
</div>
);
};

export default React.memo(EmptyNotifications);
Loading

0 comments on commit 8967d0a

Please sign in to comment.