refactor: fixed code refactor and added new slices and selector
This commit is contained in:
@@ -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 }}
|
||||
/>
|
||||
<div className="py-0 d-flex flex-row align-items-center">
|
||||
<div className="py-0 d-flex align-items-center">
|
||||
<span className="font-size-12 text-gray-500 line-height-20">
|
||||
<span>{courseName}</span>
|
||||
<span className="text-light-700 px-1.5">{intl.formatMessage(messages.fullStop)}</span>
|
||||
|
||||
@@ -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 (
|
||||
<div className="pb-2">
|
||||
<div className="d-flex flex-row justify-content-between align-items-center">
|
||||
<div className="d-flex justify-content-between align-items-center">
|
||||
<span className="text-gray-500">
|
||||
{section === 'today' && intl.formatMessage(messages.notificationTodayHeading)}
|
||||
{section === 'earlier' && intl.formatMessage(messages.notificationEarlierHeading)}
|
||||
</span>
|
||||
{notifications?.length > 0 && (section === 'earlier' ? today.length === 0 : true) && (
|
||||
<span className="text-info-500 line-height-24 cursor-pointer" onClick={handleMarkAllAsRead}>
|
||||
<Button
|
||||
variant="link"
|
||||
className="text-info-500 line-height-24 font-size-14 text-decoration-none"
|
||||
onClick={handleMarkAllAsRead}
|
||||
>
|
||||
{intl.formatMessage(messages.notificationMarkAsRead)}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{items.map((notification) => (
|
||||
@@ -67,7 +70,7 @@ const NotificationSections = () => {
|
||||
{renderNotificationSection('today', today)}
|
||||
{renderNotificationSection('earlier', earlier)}
|
||||
{paginationData.currentPage < paginationData.numPages && (
|
||||
<Button variant="primary" className="w-100 bg-primary-500" onClick={() => {}}>
|
||||
<Button variant="primary" className="w-100 bg-primary-500" onClick={updatePagination}>
|
||||
{intl.formatMessage(messages.loadMoreNotifications)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -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) => (
|
||||
<Tab
|
||||
@@ -47,4 +48,4 @@ const NotificationTabs = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationTabs;
|
||||
export default React.memo(NotificationTabs);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = () => {
|
||||
<Popover
|
||||
id="notificationTray"
|
||||
data-testid="notificationTray"
|
||||
className={classNames('notification-tray-container overflow-auto rounded-0 border-0', {
|
||||
className={classNames('overflow-auto rounded-0 border-0', {
|
||||
'w-100': !isOnMediumScreen && !isOnLargeScreen,
|
||||
'medium-screen': isOnMediumScreen,
|
||||
'large-screen': isOnLargeScreen,
|
||||
@@ -88,7 +88,7 @@ const Notifications = () => {
|
||||
<Badge
|
||||
pill
|
||||
variant="danger"
|
||||
className="font-weight-normal px-1 font-size-9 notification-badge"
|
||||
className="font-weight-normal px-1 notification-badge"
|
||||
>
|
||||
{notificationCounts.count}
|
||||
</Badge>
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user