feat: added redux store implementation
This commit is contained in:
@@ -1,58 +1,34 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
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 { AppContext } from '@edx/frontend-platform/react';
|
||||
import {
|
||||
CheckCircle, HelpOutline, QuestionAnswerOutline, Verified, Report, EditOutline, ThumbUpOutline, PostOutline,
|
||||
} from '@edx/paragon/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { messages } from './messages';
|
||||
import timeLocale from '../common/time-locale';
|
||||
import { markNotificationsAsRead } from './data/thunks';
|
||||
import { getIconByType } from './utils';
|
||||
|
||||
const NotificationRowItem = ({ notification }) => {
|
||||
const intl = useIntl();
|
||||
timeago.register('time-locale', timeLocale);
|
||||
const { authenticatedUser } = useContext(AppContext);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const getIconByType = (type) => {
|
||||
const iconMap = {
|
||||
post: { icon: PostOutline, class: 'text-primary-500' },
|
||||
help: { icon: HelpOutline, class: 'text-primary-500' },
|
||||
respond: { icon: QuestionAnswerOutline, class: 'text-primary-500' },
|
||||
comment: { icon: QuestionAnswerOutline, class: 'text-primary-500' },
|
||||
question: { icon: QuestionAnswerOutline, class: 'text-primary-500' },
|
||||
answer: { icon: CheckCircle, class: 'text-success' },
|
||||
endorsed: { icon: Verified, class: 'text-primary-500' },
|
||||
reported: { icon: Report, class: 'text-danger-500' },
|
||||
postLiked: { icon: ThumbUpOutline, class: 'text-primary-500' },
|
||||
commentLiked: { icon: ThumbUpOutline, class: 'text-primary-500' },
|
||||
edited: { icon: EditOutline, class: 'text-primary-500' },
|
||||
};
|
||||
return iconMap[type] || null;
|
||||
};
|
||||
const handleRedirectToURL = useCallback(() => {
|
||||
dispatch(markNotificationsAsRead(notification.id));
|
||||
window.open(notification.contentUrl, '_blank');
|
||||
}, [notification]);
|
||||
|
||||
const getContentMessageByType = useCallback(() => {
|
||||
const contentMessage = {
|
||||
post: messages.notificationPostedContent,
|
||||
help: messages.notificationHelpedContent,
|
||||
respond: (authenticatedUser && authenticatedUser.username) !== notification.author
|
||||
? messages.notificationResponseOnOtherPostLabel : null,
|
||||
comment: notification.targetUser
|
||||
? messages.notificationCommentedOnLabel : messages.notificationCommentedOnYourPostLabel,
|
||||
question: messages.notificationQuestionLabel,
|
||||
answer: messages.notificationAnswerLabel,
|
||||
endorsed: messages.notificationEndorsedLabel,
|
||||
reported: messages.notificationReportedLabel,
|
||||
postLiked: messages.notificationPostLikedLabel,
|
||||
commentLiked: messages.notificationCommentLikedLabel,
|
||||
edited: messages.notificationEditedLabel,
|
||||
};
|
||||
return contentMessage[notification.type] ? intl.formatMessage(contentMessage[notification.type]) : null;
|
||||
}, [authenticatedUser, notification, intl]);
|
||||
const handleMarkAllAsRead = useCallback(() => {
|
||||
dispatch(markNotificationsAsRead(notification.id));
|
||||
}, [notification.id]);
|
||||
|
||||
const iconComponent = getIconByType(notification.type);
|
||||
|
||||
return (
|
||||
<div className="d-flex mb-2 align-items-center">
|
||||
<Icon
|
||||
@@ -60,36 +36,24 @@ const NotificationRowItem = ({ notification }) => {
|
||||
style={{ height: '23.33px', width: '23.33px' }}
|
||||
className={iconComponent && `${iconComponent.class} mr-4`}
|
||||
/>
|
||||
<div className="d-flex w-100" style={{ borderRadius: '100%' }}>
|
||||
<div className="d-flex w-100">
|
||||
<div className="d-flex align-items-center w-100">
|
||||
<div className="py-2 w-100">
|
||||
<span className="line-height-24 px-0 text-primary-500 mb-2 w-100 notification-item-content overflow-hidden">
|
||||
{notification?.respondingUser } {' '}
|
||||
<span className="text-gray-500">{getContentMessageByType()} </span>
|
||||
{notification?.targetUser && (
|
||||
<>
|
||||
{notification.targetUser}
|
||||
<span className="text-gray-500">
|
||||
{(authenticatedUser && authenticatedUser.username) !== notification.author
|
||||
? intl.formatMessage(messages.notificationResponseOnOtherPostLabel)
|
||||
: intl.formatMessage(messages.notificationResponseOnYourPostLabel)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<a className="text-primary-500" href={notification.URL}>
|
||||
{' '}{notification?.notificationContent}
|
||||
</a>
|
||||
</span>
|
||||
<div className="w-100 px-0 py-0 d-flex flex-row align-items-center">
|
||||
<div className="py-2 w-100 px-0 cursor-pointer" onClick={handleRedirectToURL}>
|
||||
<span
|
||||
className="line-height-24 text-gray-700 mb-2 notification-item-content overflow-hidden"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: notification.content }}
|
||||
/>
|
||||
<div className="py-0 d-flex flex-row align-items-center">
|
||||
<span className="font-size-12 text-gray-500 line-height-20">
|
||||
<span>{notification?.courseName}</span>
|
||||
<span className="text-light-700 px-1.5">{intl.formatMessage(messages.fullStop)}</span>
|
||||
<span>{timeago.format(notification?.time, 'time-locale')}</span>
|
||||
<span>{timeago.format(notification?.createdAt, 'time-locale')}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!notification.isRead && (
|
||||
<div className="d-flex py-1.5 px-1.5 ml-2">
|
||||
{!notification.lastRead && (
|
||||
<div className="d-flex py-1.5 px-1.5 ml-2 cursor-pointer" onClick={handleMarkAllAsRead}>
|
||||
<span className="bg-brand-500 rounded unread" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,60 +1,76 @@
|
||||
import React from 'react';
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
import React, { useCallback } from 'react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { Button } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import { messages } from './messages';
|
||||
import NotificationRowItem from './NotificationRowItem';
|
||||
import { getNotifications } from './data/selectors';
|
||||
import {
|
||||
getSelectedAppNotificationIds, getSelectedAppName, getNotificationsByIds, getPaginationData,
|
||||
} from './data/selectors';
|
||||
import { splitNotificationsByTime } from './utils';
|
||||
import { markAllNotificationsAsRead } from './data/thunks';
|
||||
|
||||
const NotificationSections = ({ handleLoadMoreNotification, loadMoreCount }) => {
|
||||
const NotificationSections = ({ handleLoadMoreNotification }) => {
|
||||
const intl = useIntl();
|
||||
const notifications = useSelector(getNotifications());
|
||||
const { TODAY, EARLIER, totalCount } = notifications || {};
|
||||
const selectedAppName = useSelector(getSelectedAppName());
|
||||
const notificationIds = useSelector(getSelectedAppNotificationIds(selectedAppName));
|
||||
const notifications = useSelector(getNotificationsByIds(notificationIds));
|
||||
const paginationData = useSelector(getPaginationData());
|
||||
const { today = [], earlier = [] } = splitNotificationsByTime(notifications);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleMarkAllAsRead = useCallback(() => {
|
||||
dispatch(markAllNotificationsAsRead(selectedAppName));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedAppName]);
|
||||
|
||||
return (
|
||||
notifications && (
|
||||
<div className="mt-4 px-4">
|
||||
<div className="d-flex flex-row justify-content-between pb-2">
|
||||
{TODAY && TODAY.length > 0 && (
|
||||
{today.length > 0 && (
|
||||
<>
|
||||
<span className="text-gray-500">
|
||||
{ intl.formatMessage(messages.notificationTodayHeading)}
|
||||
</span>
|
||||
{totalCount > 0 && (
|
||||
<span className="text-info-500 line-height-24">
|
||||
{today.length + earlier.length > 0 && (
|
||||
<span className="text-info-500 line-height-24 cursor-pointer" onClick={handleMarkAllAsRead}>
|
||||
{intl.formatMessage(messages.notificationMarkAsRead)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{TODAY && TODAY.map(
|
||||
{today.map(
|
||||
(notification) => <NotificationRowItem notification={notification} />,
|
||||
)}
|
||||
|
||||
<div className="d-flex flex-row justify-content-between pb-2">
|
||||
<span className="text-gray-500">
|
||||
{EARLIER && EARLIER.length > 0
|
||||
{earlier.length > 0
|
||||
&& intl.formatMessage(messages.notificationEarlierHeading)}
|
||||
</span>
|
||||
{totalCount > 0 && TODAY && TODAY.length === 0 && (
|
||||
<span className="text-info-500 line-height-24">
|
||||
{intl.formatMessage(messages.notificationMarkAsRead)}
|
||||
</span>
|
||||
{today.length + earlier.length > 0 && today.length === 0 && (
|
||||
<span className="text-info-500 line-height-24 cursor-pointer" onClick={handleMarkAllAsRead}>
|
||||
{intl.formatMessage(messages.notificationMarkAsRead)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{EARLIER && EARLIER.map(
|
||||
(notification) => <NotificationRowItem notification={notification} />,
|
||||
)}
|
||||
{loadMoreCount < totalCount && (
|
||||
<Button
|
||||
variant="primary"
|
||||
className="w-100 bg-primary-500"
|
||||
onClick={() => handleLoadMoreNotification(loadMoreCount + 10)}
|
||||
>
|
||||
{intl.formatMessage(messages.loadMoreNotifications)}
|
||||
</Button>
|
||||
)}
|
||||
{earlier.map(
|
||||
(notification) => <NotificationRowItem notification={notification} />,
|
||||
)}
|
||||
{paginationData.currentPage < paginationData.numPages && (
|
||||
<Button
|
||||
variant="primary"
|
||||
className="w-100 bg-primary-500"
|
||||
onClick={handleLoadMoreNotification}
|
||||
>
|
||||
{intl.formatMessage(messages.loadMoreNotifications)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
@@ -62,7 +78,6 @@ const NotificationSections = ({ handleLoadMoreNotification, loadMoreCount }) =>
|
||||
|
||||
NotificationSections.propTypes = {
|
||||
handleLoadMoreNotification: PropTypes.func.isRequired,
|
||||
loadMoreCount: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(NotificationSections);
|
||||
|
||||
@@ -4,46 +4,46 @@ import React, {
|
||||
import { Tabs, Tab } from '@edx/paragon';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import NotificationSections from './NotificationSections';
|
||||
import { getNotificationTotalUnseenCounts, getSelectedAppName } from './data/selectors';
|
||||
import { getNotificationTabsCount, getSelectedAppName, getNotificationTabs } from './data/selectors';
|
||||
import { fetchNotificationList, markNotificationsAsSeen } from './data/thunks';
|
||||
import { notificationTabsOptions } from './data/constants';
|
||||
|
||||
const NotificationTabs = () => {
|
||||
const notificationUnseenCounts = useSelector(getNotificationTotalUnseenCounts());
|
||||
const selectedappName = useSelector(getSelectedAppName());
|
||||
const [activeTab, setActiveTab] = useState(notificationTabsOptions[0].key);
|
||||
const [loadMoreCount, setLoadMoreCount] = useState(10);
|
||||
const notificationUnseenCounts = useSelector(getNotificationTabsCount());
|
||||
const notificationTabs = useSelector(getNotificationTabs());
|
||||
const selectedAppName = useSelector(getSelectedAppName());
|
||||
const [activeTab, setActiveTab] = useState(selectedAppName);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchNotificationList({
|
||||
appName: activeTab, notificationCount: loadMoreCount, page, pageSize: 10,
|
||||
}));
|
||||
dispatch(markNotificationsAsSeen(activeTab));
|
||||
}, [dispatch, activeTab, loadMoreCount, page]);
|
||||
dispatch(fetchNotificationList({ appName: activeTab, page, pageSize: 10 }));
|
||||
if (selectedAppName) { dispatch(markNotificationsAsSeen(selectedAppName)); }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeTab, page, selectedAppName]);
|
||||
|
||||
const handleActiveTab = useCallback((tab) => {
|
||||
setActiveTab(tab);
|
||||
setPage(1);
|
||||
}, []);
|
||||
|
||||
const handleLoadMoreNotification = useCallback((count) => {
|
||||
setLoadMoreCount(count);
|
||||
const handleLoadMoreNotification = useCallback(() => {
|
||||
setPage(page + 1);
|
||||
}, [page]);
|
||||
|
||||
const tabArray = useMemo(() => notificationTabsOptions?.map((option) => (
|
||||
const tabArray = useMemo(() => notificationTabs?.map((option) => (
|
||||
<Tab
|
||||
eventKey={option.key}
|
||||
title={option.title}
|
||||
notification={notificationUnseenCounts.countByAppName[option.key]}
|
||||
tabClassName="pt-0 pb-2.5 px-2.5 d-flex flex-row align-items-center line-height-24"
|
||||
eventKey={option}
|
||||
title={option}
|
||||
notification={notificationUnseenCounts[option]}
|
||||
tabClassName="pt-0 pb-2.5 px-2.5 d-flex flex-row align-items-center line-height-24 text-capitalize"
|
||||
>
|
||||
{option.key === selectedappName
|
||||
&& <NotificationSections handleLoadMoreNotification={handleLoadMoreNotification} loadMoreCount={loadMoreCount} />}
|
||||
{option === selectedAppName && (
|
||||
<NotificationSections
|
||||
handleLoadMoreNotification={handleLoadMoreNotification}
|
||||
/>
|
||||
)}
|
||||
</Tab>
|
||||
)), [notificationUnseenCounts, handleLoadMoreNotification, loadMoreCount, selectedappName]);
|
||||
)), [notificationUnseenCounts, handleLoadMoreNotification, selectedAppName, notificationTabs]);
|
||||
|
||||
// This code is used to replace More... text to More to match the UI
|
||||
const buttons = document.getElementsByClassName('dropdown-toggle');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, {
|
||||
useState, useCallback, useEffect, useRef,
|
||||
} from 'react';
|
||||
@@ -9,8 +10,8 @@ import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import classNames from 'classnames';
|
||||
import NotificationTabs from './NotificationTabs';
|
||||
import { getNotificationTotalUnseenCounts, getNotificationStatus } from './data/selectors';
|
||||
import { fetchNotificationsCountsList } from './data/thunks';
|
||||
import { getNotificationTabsCount } from './data/selectors';
|
||||
import { resetNotificationState } from './data/thunks';
|
||||
import { messages } from './messages';
|
||||
import { useIsOnDesktop, useIsOnXLDesktop } from './data/hook';
|
||||
|
||||
@@ -20,32 +21,23 @@ const Notifications = () => {
|
||||
const popoverRef = useRef(null);
|
||||
const buttonRef = useRef(null);
|
||||
const dispatch = useDispatch();
|
||||
const notificationStatus = useSelector(getNotificationStatus());
|
||||
const notificationCounts = useSelector(getNotificationTotalUnseenCounts());
|
||||
const notificationCounts = useSelector(getNotificationTabsCount());
|
||||
const isOnDesktop = useIsOnDesktop();
|
||||
const isOnXLDesktop = useIsOnXLDesktop();
|
||||
|
||||
useEffect(() => {
|
||||
if (notificationStatus === 'idle') {
|
||||
dispatch(fetchNotificationsCountsList());
|
||||
}
|
||||
}, [dispatch, notificationStatus]);
|
||||
|
||||
const handleNotificationTray = useCallback((value) => {
|
||||
setShowNotificationTray(value);
|
||||
if (!value) { dispatch(resetNotificationState()); }
|
||||
}, []);
|
||||
|
||||
const handleClickOutside = useCallback((event) => {
|
||||
if (popoverRef.current?.contains(event.target) !== true && buttonRef.current?.contains(event.target) !== true) {
|
||||
setShowNotificationTray(false);
|
||||
dispatch(resetNotificationState());
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (
|
||||
popoverRef.current
|
||||
&& buttonRef.current
|
||||
&& !popoverRef.current.contains(event.target)
|
||||
&& !buttonRef.current.contains(event.target)
|
||||
) {
|
||||
setShowNotificationTray(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
@@ -61,7 +53,6 @@ const Notifications = () => {
|
||||
overlay={(
|
||||
<Popover
|
||||
id="popover-positioned-bottom"
|
||||
style={{ maxHeight: 'calc(100% - 68px)', minHeight: '1220px', minWidth: '549px' }}
|
||||
className={classNames('notification-tray-container pt-4.5 pb-4.5 overflow-auto rounded-0 border-0', {
|
||||
'w-100': !isOnDesktop,
|
||||
'notificationbar-desktop-width': isOnDesktop && !isOnXLDesktop,
|
||||
@@ -70,10 +61,7 @@ const Notifications = () => {
|
||||
data-testid="notificationbar"
|
||||
>
|
||||
<div ref={popoverRef}>
|
||||
<Popover.Title
|
||||
as="h3"
|
||||
className="d-flex flex-row justify-content-between py-0 mb-4 border-0 px-4"
|
||||
>
|
||||
<Popover.Title as="h3" className="d-flex flex-row justify-content-between py-0 mb-4 border-0 px-4">
|
||||
<h2 className="text-primary-500 font-size-18 line-height-24">
|
||||
{intl.formatMessage(messages.notificationTitle)}
|
||||
</h2>
|
||||
@@ -95,8 +83,7 @@ const Notifications = () => {
|
||||
iconAs={Icon}
|
||||
variant="light"
|
||||
iconClassNames="text-primary-500"
|
||||
className="ml-4 mr-1 my-3"
|
||||
style={{ width: '36px', height: '36px' }}
|
||||
className="ml-4 mr-1 my-3 notification-button"
|
||||
/>
|
||||
<Badge
|
||||
variant="danger"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { camelCaseObject, getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
||||
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { splitNotificationsByTime } from '../utils';
|
||||
|
||||
import notificationsList from './notifications.json';
|
||||
|
||||
@@ -8,58 +7,44 @@ export const getNotificationsCountApiUrl = () => `${getConfig().LMS_BASE_URL}/ap
|
||||
export const getNotificationsApiUrl = () => `${getConfig().LMS_BASE_URL}/api/notifications/`;
|
||||
export const markNotificationsSeenApiUrl = (appName) => `${getConfig().LMS_BASE_URL}/api/notifications/mark-notifications-unseen/${appName}/`;
|
||||
|
||||
export async function getNotifications(appName, notificationCount, page, pageSize) {
|
||||
export async function getNotifications(appName, page, pageSize) {
|
||||
// const params = snakeCaseObject({ page, pageSize });
|
||||
// const { data } = await getAuthenticatedHttpClient().get(getNotificationsApiUrl(), { params });
|
||||
const data = notificationsList.notifications;
|
||||
const { data } = notificationsList;
|
||||
const startIndex = (page - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
|
||||
const { today, earlier } = splitNotificationsByTime(camelCaseObject(data));
|
||||
data = {
|
||||
discussions: {
|
||||
TODAY: today,
|
||||
EARLIER: earlier,
|
||||
},
|
||||
reminders: {
|
||||
TODAY: today,
|
||||
EARLIER: earlier,
|
||||
},
|
||||
};
|
||||
|
||||
const notifications = data[appName];
|
||||
const { TODAY = [], EARLIER = [] } = notifications || [];
|
||||
let todayNotifications = TODAY;
|
||||
let earlierNotifications = [];
|
||||
let totalCount = 0;
|
||||
|
||||
if (TODAY && EARLIER) {
|
||||
if (TODAY.length > notificationCount) {
|
||||
todayNotifications = TODAY.slice(0, notificationCount);
|
||||
} else {
|
||||
todayNotifications = TODAY;
|
||||
earlierNotifications = EARLIER.slice(0, notificationCount - TODAY.length);
|
||||
}
|
||||
totalCount = TODAY.length + EARLIER.length;
|
||||
}
|
||||
|
||||
return { TODAY: todayNotifications, EARLIER: earlierNotifications, totalCount };
|
||||
const notifications = data.slice(startIndex, endIndex);
|
||||
return { notifications: camelCaseObject(notifications), numPages: 2, currentPage: page };
|
||||
}
|
||||
|
||||
export async function getNotificationCounts() {
|
||||
// const { data } = await getAuthenticatedHttpClient().get(getNotificationsCountApiUrl());
|
||||
const data = {
|
||||
count: 40,
|
||||
count: 45,
|
||||
count_by_app_name: {
|
||||
reminders: 10,
|
||||
discussions: 20,
|
||||
grades: 5,
|
||||
grades: 10,
|
||||
authoring: 5,
|
||||
},
|
||||
show_notification_tray: true,
|
||||
};
|
||||
|
||||
return data;
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
export async function markNotificationSeen(appName) {
|
||||
const { data } = await getAuthenticatedHttpClient().put(`${markNotificationsSeenApiUrl(appName)}`);
|
||||
// const { data } = await getAuthenticatedHttpClient().put(`${markNotificationsSeenApiUrl(appName)}`);
|
||||
const data = [];
|
||||
return camelCaseObject(data);
|
||||
}
|
||||
|
||||
export async function markAllNotificationRead() {
|
||||
const { data } = camelCaseObject(notificationsList);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function markNotificationRead(notificationId) {
|
||||
const { data } = camelCaseObject(notificationsList);
|
||||
return { data, id: notificationId };
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
export const notificationTabs = {
|
||||
REMINDERS: 'reminders',
|
||||
DISCUSSIONS: 'discussions',
|
||||
GRADES: 'grades',
|
||||
AUTHORING: 'authoring',
|
||||
};
|
||||
|
||||
export const notificationTabsLabel = {
|
||||
[notificationTabs.REMINDERS]: 'Reminders',
|
||||
[notificationTabs.DISCUSSIONS]: 'Discussions',
|
||||
[notificationTabs.GRADES]: 'Grades',
|
||||
[notificationTabs.AUTHORING]: 'Authoring',
|
||||
};
|
||||
|
||||
export const notificationTabsOptions = [
|
||||
{
|
||||
key: notificationTabs.REMINDERS,
|
||||
title: notificationTabsLabel[notificationTabs.REMINDERS],
|
||||
},
|
||||
{
|
||||
key: notificationTabs.DISCUSSIONS,
|
||||
title: notificationTabsLabel[notificationTabs.DISCUSSIONS],
|
||||
},
|
||||
{
|
||||
key: notificationTabs.GRADES,
|
||||
title: notificationTabsLabel[notificationTabs.GRADES],
|
||||
},
|
||||
{
|
||||
key: notificationTabs.AUTHORING,
|
||||
title: notificationTabsLabel[notificationTabs.AUTHORING],
|
||||
},
|
||||
];
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": "post",
|
||||
"content": "<p><b>SCM_Lead</b> posts <b>Hello and welcome to SC0x!</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_content_url": "",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:46:11.979531Z"
|
||||
@@ -15,7 +15,7 @@
|
||||
"type": "help",
|
||||
"content": "<p><b>MITx_Learner</b> asked <b>What grade does a student need to get in order to pass the course and earn a certificate?</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_content_url": "",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
@@ -25,7 +25,7 @@
|
||||
"type": "post",
|
||||
"content": "<p><b>SCM_Lead</b> posts <b>Hello and welcome to SC0x!</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_content_url": "",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:46:11.979531Z"
|
||||
@@ -35,7 +35,7 @@
|
||||
"type": "respond",
|
||||
"content": "<p><b>MITx_Learner</b> responded <b>Can't find linear regression in section 3 review</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_content_url": "",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
@@ -45,7 +45,7 @@
|
||||
"type": "comment",
|
||||
"content": "<p><b>MITx_Learner</b> commented on <b>MITx_Expert's</b> response on a post your following <b>Can't find linear regression in section 3 review</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_content_url": "",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
@@ -55,7 +55,7 @@
|
||||
"type": "question",
|
||||
"content": "<p><b>MITx_Learner</b> commented <b>Examples of quadratic equations in supply chains</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_content_url": "",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
@@ -65,7 +65,7 @@
|
||||
"type": "answer",
|
||||
"content": "<p><b>MITx_Expert</b> answered <b>Examples of quadratic equations in supply chains</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_content_url": "",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
@@ -75,7 +75,7 @@
|
||||
"type": "comment",
|
||||
"content": "<p><b>MITx_Learner</b> commented <b>Examples of quadratic equations in supply chains</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_content_url": "",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
@@ -85,7 +85,47 @@
|
||||
"type": "comment",
|
||||
"content": "<p><b>MITx_Learner</b> commented on <b>MITx_Expert's</b>what grade does a student need to get in order to pass the course and earn a certificate?</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_content_url": "",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "comment",
|
||||
"content": "<p><b>MITx_Learner</b> commented on your response in <b>Convexity of f(x)=1/x , x>1</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "answer",
|
||||
"content": "<p><b>SCM_Lead’s</b> response has been marked as answer in your post <b>Quiz in section 3 - Please explain the F-Significance value</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "endorsed",
|
||||
"content": "<p>Your response has been endorsed in <b>Quiz in section 3 - Please explain the F-Significance value</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "reported",
|
||||
"content": "<p><b>MITx Learner’s</b> post has been reported <b>“Here are the exam answers. Question 1 - CSA stands for Compliance Safety Ac...”</b></p>",
|
||||
"course_name": "Supply Chain Analytics",
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export const getNotificationStatus = () => state => state.notifications.notificationStatus;
|
||||
export const getNotificationTotalUnseenCounts = () => state => state.notifications.totalUnseenCounts;
|
||||
export const getNotifications = () => state => state.notifications.notifications;
|
||||
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;
|
||||
|
||||
@@ -6,40 +6,24 @@ export const LOADING = 'loading';
|
||||
export const LOADED = 'loaded';
|
||||
export const FAILED = 'failed';
|
||||
export const DENIED = 'denied';
|
||||
// today or earlier logic will shift on component level
|
||||
|
||||
const initialState = {
|
||||
notificationStatus: 'idle',
|
||||
appName: 'reminders',
|
||||
appsId: [],
|
||||
apps: {},
|
||||
notifications: {},
|
||||
tabsCount: {},
|
||||
showNotificationTray: false,
|
||||
pagination: {
|
||||
count: 10,
|
||||
numPages: 1,
|
||||
currentPage: 1,
|
||||
},
|
||||
};
|
||||
const slice = createSlice({
|
||||
name: 'notifications',
|
||||
initialState: {
|
||||
notificationStatus: 'idle',
|
||||
appName: 'discussions',
|
||||
appsId: ['reminders', 'discussions', 'grades', 'authoring'],
|
||||
apps: {
|
||||
reminders: ['notification_1', 'notification_2'],
|
||||
discussions: ['notification_3'],
|
||||
grades: ['notification_4', 'notification_5'],
|
||||
authoring: ['notification_6'],
|
||||
},
|
||||
notifications: {
|
||||
notification_1: {},
|
||||
notification_2: {},
|
||||
notification_3: {},
|
||||
notification_4: {},
|
||||
notification_5: {},
|
||||
notification_6: {},
|
||||
},
|
||||
tabsCount: {
|
||||
reminders: 0,
|
||||
discussions: 0,
|
||||
grades: 0,
|
||||
authoring: 0,
|
||||
totalCount: 0,
|
||||
},
|
||||
pagination: {
|
||||
count: 90,
|
||||
numPages: 9,
|
||||
currentPage: 1,
|
||||
},
|
||||
},
|
||||
initialState,
|
||||
reducers: {
|
||||
fetchNotificationDenied: (state, { payload }) => {
|
||||
state.appName = payload.appName;
|
||||
@@ -50,12 +34,24 @@ const slice = createSlice({
|
||||
state.notificationStatus = FAILED;
|
||||
},
|
||||
fetchNotificationRequest: (state, { payload }) => {
|
||||
if (state.appName !== payload.appName) { state.apps[payload.appName] = []; }
|
||||
state.appName = payload.appName;
|
||||
state.notificationStatus = LOADING;
|
||||
},
|
||||
fetchNotificationSuccess: (state, { payload }) => {
|
||||
state.notifications = payload;
|
||||
const { notifications, numPages, currentPage } = payload;
|
||||
const newNotificationIds = notifications.map(notification => notification.id.toString());
|
||||
const existingNotificationIds = state.apps[state.appName];
|
||||
const notificationsKeyValuePair = notifications.reduce((acc, obj) => { acc[obj.id] = obj; return acc; }, {});
|
||||
const currentAppCount = state.tabsCount[state.appName];
|
||||
|
||||
state.apps[state.appName] = Array.from(new Set([...existingNotificationIds, ...newNotificationIds]));
|
||||
state.notifications = { ...state.notifications, ...notificationsKeyValuePair };
|
||||
state.tabsCount.count -= currentAppCount;
|
||||
state.tabsCount[state.appName] = 0;
|
||||
state.notificationStatus = LOADED;
|
||||
state.pagination.numPages = numPages;
|
||||
state.pagination.currentPage = currentPage;
|
||||
},
|
||||
fetchNotificationsCountDenied: (state) => {
|
||||
state.notificationStatus = DENIED;
|
||||
@@ -67,7 +63,11 @@ const slice = createSlice({
|
||||
state.notificationStatus = LOADING;
|
||||
},
|
||||
fetchNotificationsCountSuccess: (state, { payload }) => {
|
||||
state.tabsCount = payload;
|
||||
const { countByAppName, count, showNotificationTray } = payload;
|
||||
state.tabsCount = { count, ...countByAppName };
|
||||
state.appsId = Object.keys(countByAppName);
|
||||
state.apps = Object.fromEntries(Object.keys(countByAppName).map(key => [key, []]));
|
||||
state.showNotificationTray = showNotificationTray;
|
||||
state.notificationStatus = LOADED;
|
||||
},
|
||||
markNotificationsAsSeenRequest: (state) => {
|
||||
@@ -82,6 +82,39 @@ const slice = createSlice({
|
||||
markNotificationsAsSeenFailure: (state) => {
|
||||
state.notificationStatus = FAILED;
|
||||
},
|
||||
markAllNotificationsAsReadRequest: (state) => {
|
||||
state.notificationStatus = LOADING;
|
||||
},
|
||||
markAllNotificationsAsReadSuccess: (state) => {
|
||||
const date = new Date().toISOString();
|
||||
const updatedNotifications = Object.entries(state.notifications)
|
||||
.filter(([key]) => state.apps[state.appName].includes(key))
|
||||
.map(([, value]) => ({ ...value, lastRead: date }));
|
||||
|
||||
state.notifications = updatedNotifications;
|
||||
state.notificationStatus = LOADED;
|
||||
},
|
||||
markAllNotificationsAsReadDenied: (state) => {
|
||||
state.notificationStatus = DENIED;
|
||||
},
|
||||
markAllNotificationsAsReadFailure: (state) => {
|
||||
state.notificationStatus = FAILED;
|
||||
},
|
||||
markNotificationsAsReadRequest: (state) => {
|
||||
state.notificationStatus = LOADING;
|
||||
},
|
||||
markNotificationsAsReadSuccess: (state, { payload }) => {
|
||||
const date = new Date().toISOString();
|
||||
state.notifications[payload.id] = { ...state.notifications[payload.id], lastRead: date };
|
||||
state.notificationStatus = LOADED;
|
||||
},
|
||||
markNotificationsAsReadDenied: (state) => {
|
||||
state.notificationStatus = DENIED;
|
||||
},
|
||||
markNotificationsAsReadFailure: (state) => {
|
||||
state.notificationStatus = FAILED;
|
||||
},
|
||||
resetNotificationStateRequest: () => initialState,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -98,6 +131,15 @@ export const {
|
||||
markNotificationsAsSeenSuccess,
|
||||
markNotificationsAsSeenFailure,
|
||||
markNotificationsAsSeenDenied,
|
||||
markAllNotificationsAsReadDenied,
|
||||
markAllNotificationsAsReadRequest,
|
||||
markAllNotificationsAsReadSuccess,
|
||||
markAllNotificationsAsReadFailure,
|
||||
markNotificationsAsReadDenied,
|
||||
markNotificationsAsReadRequest,
|
||||
markNotificationsAsReadSuccess,
|
||||
markNotificationsAsReadFailure,
|
||||
resetNotificationStateRequest,
|
||||
} = slice.actions;
|
||||
|
||||
export const notificationsReducer = slice.reducer;
|
||||
|
||||
@@ -3,25 +3,44 @@ import {
|
||||
fetchNotificationSuccess,
|
||||
fetchNotificationRequest,
|
||||
fetchNotificationFailure,
|
||||
fetchNotificationDenied,
|
||||
fetchNotificationsCountFailure,
|
||||
fetchNotificationsCountRequest,
|
||||
fetchNotificationsCountSuccess,
|
||||
fetchNotificationsCountDenied,
|
||||
markNotificationsAsSeenRequest,
|
||||
markNotificationsAsSeenSuccess,
|
||||
markNotificationsAsSeenFailure,
|
||||
markNotificationsAsSeenDenied,
|
||||
markNotificationsAsReadDenied,
|
||||
resetNotificationStateRequest,
|
||||
markAllNotificationsAsReadRequest,
|
||||
markAllNotificationsAsReadSuccess,
|
||||
markAllNotificationsAsReadFailure,
|
||||
markAllNotificationsAsReadDenied,
|
||||
markNotificationsAsReadRequest,
|
||||
markNotificationsAsReadSuccess,
|
||||
markNotificationsAsReadFailure,
|
||||
} from './slice';
|
||||
import { getNotifications, getNotificationCounts, markNotificationSeen } from './api';
|
||||
import {
|
||||
getNotifications, getNotificationCounts, markNotificationSeen, markAllNotificationRead, markNotificationRead,
|
||||
} from './api';
|
||||
import { getHttpErrorStatus } from '../utils';
|
||||
|
||||
export const fetchNotificationList = ({
|
||||
appName, notificationCount, page, pageSize,
|
||||
appName, page, pageSize,
|
||||
}) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(fetchNotificationRequest({ appName }));
|
||||
const data = await getNotifications(appName, notificationCount, page, pageSize);
|
||||
const data = await getNotifications(appName, page, pageSize);
|
||||
dispatch(fetchNotificationSuccess(data));
|
||||
} catch (errors) {
|
||||
dispatch(fetchNotificationFailure({ appName }));
|
||||
} catch (error) {
|
||||
if (getHttpErrorStatus(error) === 403) {
|
||||
dispatch(fetchNotificationDenied(appName));
|
||||
} else {
|
||||
dispatch(fetchNotificationFailure(appName));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -32,8 +51,44 @@ export const fetchAppsNotificationCount = () => (
|
||||
dispatch(fetchNotificationsCountRequest());
|
||||
const data = await getNotificationCounts();
|
||||
dispatch(fetchNotificationsCountSuccess(camelCaseObject(data)));
|
||||
} catch (errors) {
|
||||
dispatch(fetchNotificationsCountFailure());
|
||||
} catch (error) {
|
||||
if (getHttpErrorStatus(error) === 403) {
|
||||
dispatch(fetchNotificationsCountDenied());
|
||||
} else {
|
||||
dispatch(fetchNotificationsCountFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const markAllNotificationsAsRead = (appName) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(markAllNotificationsAsReadRequest({ appName }));
|
||||
const data = await markAllNotificationRead(appName);
|
||||
dispatch(markAllNotificationsAsReadSuccess(data));
|
||||
} catch (error) {
|
||||
if (getHttpErrorStatus(error) === 403) {
|
||||
dispatch(markAllNotificationsAsReadDenied());
|
||||
} else {
|
||||
dispatch(markAllNotificationsAsReadFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const markNotificationsAsRead = (notificationId) => (
|
||||
async (dispatch) => {
|
||||
try {
|
||||
dispatch(markNotificationsAsReadRequest({ notificationId }));
|
||||
const data = await markNotificationRead(notificationId);
|
||||
dispatch(markNotificationsAsReadSuccess(data));
|
||||
} catch (error) {
|
||||
if (getHttpErrorStatus(error) === 403) {
|
||||
dispatch(markNotificationsAsReadDenied());
|
||||
} else {
|
||||
dispatch(markNotificationsAsReadFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -44,8 +99,16 @@ export const markNotificationsAsSeen = (appName) => (
|
||||
dispatch(markNotificationsAsSeenRequest({ appName }));
|
||||
const data = await markNotificationSeen(appName);
|
||||
dispatch(markNotificationsAsSeenSuccess(data));
|
||||
} catch (errors) {
|
||||
dispatch(markNotificationsAsSeenFailure({ appName }));
|
||||
} catch (error) {
|
||||
if (getHttpErrorStatus(error) === 403) {
|
||||
dispatch(markNotificationsAsSeenDenied());
|
||||
} else {
|
||||
dispatch(markNotificationsAsSeenFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const resetNotificationState = () => (
|
||||
async (dispatch) => { dispatch(resetNotificationStateRequest()); }
|
||||
);
|
||||
|
||||
@@ -22,76 +22,6 @@ export const messages = defineMessages({
|
||||
defaultMessage: 'Mark all as read',
|
||||
description: 'Mark all Notifications as read',
|
||||
},
|
||||
notificationPostedContent: {
|
||||
id: 'notification.posted.content',
|
||||
defaultMessage: 'posted',
|
||||
description: 'Display notification content for post type',
|
||||
},
|
||||
notificationHelpedContent: {
|
||||
id: 'notification.helped.content',
|
||||
defaultMessage: 'asked',
|
||||
description: 'Display notification content for help type',
|
||||
},
|
||||
notificationRespondedLabel: {
|
||||
id: 'notification.responded.label',
|
||||
defaultMessage: 'responded to a post you’re following',
|
||||
description: 'Display notification content for respond type',
|
||||
},
|
||||
notificationCommentedOnLabel: {
|
||||
id: 'notification.commented.on.label',
|
||||
defaultMessage: 'commented on',
|
||||
description: 'Display notification content for comment type',
|
||||
},
|
||||
notificationResponseOnOtherPostLabel: {
|
||||
id: 'notification.response.on.other.post.label',
|
||||
defaultMessage: 'response on a post you’re following:',
|
||||
description: 'Display notification content for comment type for other posts',
|
||||
},
|
||||
notificationQuestionLabel: {
|
||||
id: 'notification.question.label',
|
||||
defaultMessage: 'responded to your question',
|
||||
description: 'Display notification content for question type',
|
||||
},
|
||||
notificationResponseOnYourPostLabel: {
|
||||
id: 'notification.response.on.your.post.label',
|
||||
defaultMessage: 'response to your post',
|
||||
description: 'Display notification content for comment type for your post',
|
||||
},
|
||||
notificationCommentedOnYourPostLabel: {
|
||||
id: 'notification.commented.on.your.post.label',
|
||||
defaultMessage: 'commented on your response in',
|
||||
description: 'Display notification content for comment type on your response',
|
||||
},
|
||||
notificationAnswerLabel: {
|
||||
id: 'notification.answer.label',
|
||||
defaultMessage: 'response has been marked as answer in your post',
|
||||
description: 'Display notification content for answer type',
|
||||
},
|
||||
notificationEndorsedLabel: {
|
||||
id: 'notification.endorsed.label',
|
||||
defaultMessage: 'Your response has been endorsed in',
|
||||
description: 'Display notification content for endorsed type',
|
||||
},
|
||||
notificationReportedLabel: {
|
||||
id: 'notification.reported.label',
|
||||
defaultMessage: 'post has been reported',
|
||||
description: 'Display notification content for reported type',
|
||||
},
|
||||
notificationPostLikedLabel: {
|
||||
id: 'notification.post.liked.label',
|
||||
defaultMessage: 'liked your post',
|
||||
description: 'Display notification content for post liked type',
|
||||
},
|
||||
notificationCommentLikedLabel: {
|
||||
id: 'notification.comment.liked.label',
|
||||
defaultMessage: 'liked your response in',
|
||||
description: 'Display notification content for response liked type',
|
||||
},
|
||||
notificationEditedLabel: {
|
||||
id: 'notification.edited.label',
|
||||
defaultMessage: 'edited your post',
|
||||
description: 'Display notification content for edited type',
|
||||
},
|
||||
fullStop: {
|
||||
id: 'notification.fullStop',
|
||||
defaultMessage: '•',
|
||||
|
||||
@@ -1,24 +1,50 @@
|
||||
import {
|
||||
CheckCircle, HelpOutline, QuestionAnswerOutline, Verified, Report, EditOutline, ThumbUpOutline, PostOutline,
|
||||
} from '@edx/paragon/icons';
|
||||
|
||||
/**
|
||||
* Get HTTP Error status from generic error.
|
||||
* @param error Generic caught error.
|
||||
* @returns {number|null}
|
||||
*/
|
||||
export const getHttpErrorStatus = error => error?.customAttributes?.httpErrorStatus ?? error?.response?.status;
|
||||
|
||||
export const splitNotificationsByTime = (notificationList) => {
|
||||
const currentTime = Date.now();
|
||||
const twentyFourHoursAgo = currentTime - (24 * 60 * 60 * 1000);
|
||||
|
||||
const { today, earlier } = 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);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
{ today: [], earlier: [] },
|
||||
);
|
||||
let splittedData = [];
|
||||
if (notificationList.length > 0) {
|
||||
const currentTime = Date.now();
|
||||
const twentyFourHoursAgo = currentTime - (24 * 60 * 60 * 1000);
|
||||
|
||||
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);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
{ today: [], earlier: [] },
|
||||
);
|
||||
}
|
||||
const { today, earlier } = splittedData;
|
||||
return { today, earlier };
|
||||
};
|
||||
|
||||
export const getNotificationCount = (notificationCounts, appName) => {
|
||||
const { countByAppName } = notificationCounts;
|
||||
return countByAppName[appName] || 0;
|
||||
export const getIconByType = (type) => {
|
||||
const iconMap = {
|
||||
post: { icon: PostOutline, class: 'text-primary-500' },
|
||||
help: { icon: HelpOutline, class: 'text-primary-500' },
|
||||
respond: { icon: QuestionAnswerOutline, class: 'text-primary-500' },
|
||||
comment: { icon: QuestionAnswerOutline, class: 'text-primary-500' },
|
||||
question: { icon: QuestionAnswerOutline, class: 'text-primary-500' },
|
||||
answer: { icon: CheckCircle, class: 'text-success' },
|
||||
endorsed: { icon: Verified, class: 'text-primary-500' },
|
||||
reported: { icon: Report, class: 'text-danger-500' },
|
||||
postLiked: { icon: ThumbUpOutline, class: 'text-primary-500' },
|
||||
commentLiked: { icon: ThumbUpOutline, class: 'text-primary-500' },
|
||||
edited: { icon: EditOutline, class: 'text-primary-500' },
|
||||
};
|
||||
return iconMap[type] || null;
|
||||
};
|
||||
|
||||
@@ -141,6 +141,18 @@ $white: #fff;
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
.cursor-pointer{
|
||||
cursor: pointer;
|
||||
}
|
||||
#popover-positioned-bottom{
|
||||
max-height: calc(100% - 68px);
|
||||
min-height: 1220px;
|
||||
min-width: 549px;
|
||||
}
|
||||
.notification-button{
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.notification-badge{
|
||||
position: absolute;
|
||||
margin-top: 18px;
|
||||
@@ -173,6 +185,12 @@ $white: #fff;
|
||||
}
|
||||
.notification-content{
|
||||
.notification-item-content{
|
||||
p{
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
b{
|
||||
color: #00262B;
|
||||
}
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
@@ -7,11 +7,25 @@ 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 { fetchAppsNotificationCount } from '../Notifications/data/thunks';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const AuthenticatedUserDropdown = ({ intl, username }) => {
|
||||
const showNotificationTray = useSelector(getNotificationTrayStatus());
|
||||
const notificationStatus = useSelector(getNotificationStatus());
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (notificationStatus === 'idle') {
|
||||
dispatch(fetchAppsNotificationCount());
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [notificationStatus]);
|
||||
|
||||
const dashboardMenuItem = (
|
||||
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}>
|
||||
{intl.formatMessage(messages.dashboard)}
|
||||
@@ -21,7 +35,7 @@ const AuthenticatedUserDropdown = ({ intl, username }) => {
|
||||
return (
|
||||
<>
|
||||
<a className="text-gray-700" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
|
||||
<Notifications />
|
||||
{showNotificationTray && <Notifications />}
|
||||
<Dropdown className="user-dropdown ml-3">
|
||||
<Dropdown.Toggle variant="outline-primary">
|
||||
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
|
||||
|
||||
Reference in New Issue
Block a user