diff --git a/src/Notifications/NotificationRowItem.jsx b/src/Notifications/NotificationRowItem.jsx index c66d356..136420a 100644 --- a/src/Notifications/NotificationRowItem.jsx +++ b/src/Notifications/NotificationRowItem.jsx @@ -1,14 +1,14 @@ import React, { useCallback } from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { Icon } from '@edx/paragon'; -import * as timeago from 'timeago.js'; -import PropTypes from 'prop-types'; import { useDispatch } from 'react-redux'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import PropTypes from 'prop-types'; +import { Icon } from '@edx/paragon'; import { Link } from 'react-router-dom'; +import * as timeago from 'timeago.js'; +import { getIconByType } from './utils'; +import { markNotificationsAsRead } from './data/thunks'; import { messages } from './messages'; import timeLocale from '../common/time-locale'; -import { markNotificationsAsRead } from './data/thunks'; -import { getIconByType } from './utils'; const NotificationRowItem = ({ id, type, contentUrl, content, courseName, createdAt, lastRead, @@ -38,7 +38,7 @@ const NotificationRowItem = ({ // eslint-disable-next-line react/no-danger dangerouslySetInnerHTML={{ __html: content }} /> -
+
{courseName} {intl.formatMessage(messages.fullStop)} diff --git a/src/Notifications/NotificationSections.jsx b/src/Notifications/NotificationSections.jsx index d9aa446..8e1c617 100644 --- a/src/Notifications/NotificationSections.jsx +++ b/src/Notifications/NotificationSections.jsx @@ -1,26 +1,21 @@ import React, { useCallback, useMemo } from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { useSelector, useDispatch } from 'react-redux'; import { Button } from '@edx/paragon'; +import { useDispatch, useSelector } from 'react-redux'; +import { useIntl } from '@edx/frontend-platform/i18n'; import isEmpty from 'lodash/isEmpty'; import { messages } from './messages'; import NotificationRowItem from './NotificationRowItem'; -import { - getSelectedAppNotificationIds, - getSelectedAppName, - getNotificationsByIds, - getPaginationData, -} from './data/selectors'; -import { splitNotificationsByTime } from './utils'; import { markAllNotificationsAsRead } from './data/thunks'; +import { selectNotificationsByIds, selectPaginationData, selectSelectedAppName } from './data/selectors'; +import { splitNotificationsByTime } from './utils'; +import { updatePaginationRequest } from './data/slice'; const NotificationSections = () => { const intl = useIntl(); const dispatch = useDispatch(); - const selectedAppName = useSelector(getSelectedAppName()); - const notificationIds = useSelector(getSelectedAppNotificationIds(selectedAppName)); - const notifications = useSelector(getNotificationsByIds(notificationIds)); - const paginationData = useSelector(getPaginationData()); + const selectedAppName = useSelector(selectSelectedAppName()); + const notifications = useSelector(selectNotificationsByIds); + const paginationData = useSelector(selectPaginationData()); const { today = [], earlier = [] } = useMemo( () => splitNotificationsByTime(notifications), [notifications], @@ -30,20 +25,28 @@ const NotificationSections = () => { dispatch(markAllNotificationsAsRead(selectedAppName)); }, [dispatch, selectedAppName]); + const updatePagination = useCallback(() => { + dispatch(updatePaginationRequest()); + }, [dispatch]); + const renderNotificationSection = (section, items) => { if (isEmpty(items)) { return null; } return (
-
+
{section === 'today' && intl.formatMessage(messages.notificationTodayHeading)} {section === 'earlier' && intl.formatMessage(messages.notificationEarlierHeading)} {notifications?.length > 0 && (section === 'earlier' ? today.length === 0 : true) && ( - + )}
{items.map((notification) => ( @@ -67,7 +70,7 @@ const NotificationSections = () => { {renderNotificationSection('today', today)} {renderNotificationSection('earlier', earlier)} {paginationData.currentPage < paginationData.numPages && ( - )} diff --git a/src/Notifications/NotificationTabs.jsx b/src/Notifications/NotificationTabs.jsx index 8468f5f..6dd395d 100644 --- a/src/Notifications/NotificationTabs.jsx +++ b/src/Notifications/NotificationTabs.jsx @@ -1,27 +1,28 @@ -import React, { - useState, useCallback, useMemo, useEffect, -} from 'react'; -import { Tabs, Tab } from '@edx/paragon'; -import { useSelector, useDispatch } from 'react-redux'; +import React, { useCallback, useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Tab, Tabs } from '@edx/paragon'; import NotificationSections from './NotificationSections'; -import { getNotificationTabsCount, getSelectedAppName, getNotificationTabs } from './data/selectors'; import { fetchNotificationList, markNotificationsAsSeen } from './data/thunks'; +import { + selectNotificationTabs, selectNotificationTabsCount, selectPaginationData, selectSelectedAppName, +} from './data/selectors'; +import { updateAppNameRequest } from './data/slice'; const NotificationTabs = () => { const dispatch = useDispatch(); - const [page, setPage] = useState(1); - const selectedAppName = useSelector(getSelectedAppName()); - const notificationUnseenCounts = useSelector(getNotificationTabsCount()); - const notificationTabs = useSelector(getNotificationTabs()); + const selectedAppName = useSelector(selectSelectedAppName()); + const notificationUnseenCounts = useSelector(selectNotificationTabsCount()); + const notificationTabs = useSelector(selectNotificationTabs()); + const paginationData = useSelector(selectPaginationData()); useEffect(() => { - dispatch(fetchNotificationList({ appName: selectedAppName, page, pageSize: 10 })); + dispatch(fetchNotificationList({ appName: selectedAppName, page: paginationData.currentPage, pageSize: 10 })); if (selectedAppName) { dispatch(markNotificationsAsSeen(selectedAppName)); } - }, [dispatch, page, selectedAppName]); + }, [dispatch, paginationData.currentPage, selectedAppName]); const handleActiveTab = useCallback((appName) => { - // dispatch(setSelectedAppName(appName)); - }, []); + dispatch(updateAppNameRequest({ appName })); + }, [dispatch]); const tabArray = useMemo(() => notificationTabs?.map((appName) => ( { ); }; -export default NotificationTabs; +export default React.memo(NotificationTabs); diff --git a/src/Notifications/data/selectors.js b/src/Notifications/data/selectors.js index ee9eb0e..2fbb78c 100644 --- a/src/Notifications/data/selectors.js +++ b/src/Notifications/data/selectors.js @@ -1,9 +1,23 @@ -export const getNotificationStatus = () => state => state.notifications.notificationStatus; -export const getNotificationTabsCount = () => state => state.notifications.tabsCount; -export const getNotificationTabs = () => state => state.notifications.appsId; -export const getSelectedAppNotificationIds = (appName) => state => state.notifications.apps[appName] ?? []; -export const getNotificationTrayStatus = () => state => state.notifications.showNotificationTray; -export const getNotificationsByIds = (notificationIds) => state => Object.entries(state.notifications.notifications) - .filter(([key]) => notificationIds.includes(key)).map(([, value]) => value); -export const getSelectedAppName = () => state => state.notifications.appName; -export const getPaginationData = () => state => state.notifications.pagination; +import { createSelector } from '@reduxjs/toolkit'; + +export const selectNotificationStatus = () => state => state.notifications.notificationStatus; + +export const selectNotificationTabsCount = () => state => state.notifications.tabsCount; + +export const selectNotificationTabs = () => state => state.notifications.appsId; + +export const selectSelectedAppNotificationIds = (appName) => state => state.notifications.apps[appName] ?? []; + +export const selectShowNotificationTray = () => state => state.notifications.showNotificationTray; + +export const selectNotifications = () => state => state.notifications.notification; + +export const selectNotificationsByIds = createSelector( + state => state.notifications.notifications, + state => state.notifications.apps[state.notifications.appName] || [], + (notifications, notificationIds) => notificationIds.map(notificationId => notifications[notificationId]), +); + +export const selectSelectedAppName = () => state => state.notifications.appName; + +export const selectPaginationData = () => state => state.notifications.pagination; diff --git a/src/Notifications/data/slice.js b/src/Notifications/data/slice.js index 7e6373b..b32c27f 100644 --- a/src/Notifications/data/slice.js +++ b/src/Notifications/data/slice.js @@ -19,6 +19,7 @@ const initialState = { count: 10, numPages: 1, currentPage: 1, + nextPage: null, }, }; const slice = createSlice({ @@ -115,6 +116,15 @@ const slice = createSlice({ state.notificationStatus = FAILED; }, resetNotificationStateRequest: () => initialState, + + updateAppNameRequest: (state, { payload }) => { + state.appName = payload.appName; + state.pagination.currentPage = 1; + }, + + updatePaginationRequest: (state) => { + state.pagination.currentPage += 1; + }, }, }); @@ -140,6 +150,8 @@ export const { markNotificationsAsReadSuccess, markNotificationsAsReadFailure, resetNotificationStateRequest, + updateAppNameRequest, + updatePaginationRequest, } = slice.actions; export const notificationsReducer = slice.reducer; diff --git a/src/Notifications/Notifications.jsx b/src/Notifications/index.jsx similarity index 86% rename from src/Notifications/Notifications.jsx rename to src/Notifications/index.jsx index 5ebb9ef..419cc4b 100644 --- a/src/Notifications/Notifications.jsx +++ b/src/Notifications/index.jsx @@ -1,19 +1,19 @@ /* eslint-disable react-hooks/exhaustive-deps */ import React, { - useState, useCallback, useEffect, useRef, + useCallback, useEffect, useRef, useState, } from 'react'; -import { NotificationsNone, Settings } from '@edx/paragon/icons'; +import { useDispatch, useSelector } from 'react-redux'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import classNames from 'classnames'; import { Badge, Icon, IconButton, OverlayTrigger, Popover, } from '@edx/paragon'; -import { useSelector, useDispatch } from 'react-redux'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import classNames from 'classnames'; -import NotificationTabs from './NotificationTabs'; -import { getNotificationTabsCount } from './data/selectors'; +import { NotificationsNone, Settings } from '@edx/paragon/icons'; +import { selectNotificationTabsCount } from './data/selectors'; import { resetNotificationState } from './data/thunks'; +import { useIsOnLargeScreen, useIsOnMediumScreen } from './data/hook'; +import NotificationTabs from './NotificationTabs'; import { messages } from './messages'; -import { useIsOnMediumScreen, useIsOnLargeScreen } from './data/hook'; const Notifications = () => { const intl = useIntl(); @@ -21,7 +21,7 @@ const Notifications = () => { const popoverRef = useRef(null); const buttonRef = useRef(null); const [showNotificationTray, setShowNotificationTray] = useState(false); - const notificationCounts = useSelector(getNotificationTabsCount()); + const notificationCounts = useSelector(selectNotificationTabsCount()); const isOnMediumScreen = useIsOnMediumScreen(); const isOnLargeScreen = useIsOnLargeScreen(); @@ -55,7 +55,7 @@ const Notifications = () => { { {notificationCounts.count} diff --git a/src/Notifications/utils.js b/src/Notifications/utils.js index c5a5655..d17eef3 100644 --- a/src/Notifications/utils.js +++ b/src/Notifications/utils.js @@ -17,11 +17,13 @@ export const splitNotificationsByTime = (notificationList) => { splittedData = notificationList.reduce( (result, notification) => { - const objectTime = new Date(notification.createdAt).getTime(); - if (objectTime >= twentyFourHoursAgo && objectTime <= currentTime) { - result.today.push(notification); - } else { - result.earlier.push(notification); + if (notification) { + const objectTime = new Date(notification.createdAt).getTime(); + if (objectTime >= twentyFourHoursAgo && objectTime <= currentTime) { + result.today.push(notification); + } else { + result.earlier.push(notification); + } } return result; }, diff --git a/src/index.scss b/src/index.scss index d9a607e..2917197 100644 --- a/src/index.scss +++ b/src/index.scss @@ -130,8 +130,8 @@ $white: #fff; font-size: 12px; } -.font-size-9{ - font-size: 9px; +.font-size-14{ + font-size: 14px; } .line-height-24{ @@ -161,15 +161,14 @@ $white: #fff; margin-top: 18px; margin-left: -21px; border: 2px solid #FFFFFF; + font-size: 9px !important; } -.notification-tray-container { +.popover{ max-height: calc(100% - 68px); min-height: 1220px; - - .popover { - - } + filter: none; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15), 0px 2px 8px rgba(0, 0, 0, 0.15); &.medium-screen { min-width: 24.313rem; diff --git a/src/learning-header/AuthenticatedUserDropdown.jsx b/src/learning-header/AuthenticatedUserDropdown.jsx index 05f1540..680b437 100644 --- a/src/learning-header/AuthenticatedUserDropdown.jsx +++ b/src/learning-header/AuthenticatedUserDropdown.jsx @@ -3,24 +3,24 @@ 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 } from '@edx/paragon'; import { useSelector, useDispatch } from 'react-redux'; -import Notifications from '../Notifications/Notifications'; -import { getNotificationTrayStatus, getNotificationStatus } from '../Notifications/data/selectors'; +import Notifications from '../Notifications'; +import { selectShowNotificationTray, selectNotificationStatus } from '../Notifications/data/selectors'; import { fetchAppsNotificationCount } from '../Notifications/data/thunks'; +import { IDLE } from '../Notifications/data/slice'; import messages from './messages'; const AuthenticatedUserDropdown = ({ intl, username }) => { - const showNotificationTray = useSelector(getNotificationTrayStatus()); - const notificationStatus = useSelector(getNotificationStatus()); + const showNotificationTray = useSelector(selectShowNotificationTray()); + const notificationStatus = useSelector(selectNotificationStatus()); const dispatch = useDispatch(); useEffect(() => { - if (notificationStatus === 'idle') { + if (notificationStatus === IDLE) { dispatch(fetchAppsNotificationCount()); } // eslint-disable-next-line react-hooks/exhaustive-deps