refactor: code and style modifications
This commit is contained in:
@@ -1,36 +1,30 @@
|
||||
/* 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 } 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 { Link } from 'react-router-dom';
|
||||
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();
|
||||
const NotificationRowItem = ({
|
||||
id, type, contentUrl, content, courseName, createdAt, lastRead,
|
||||
}) => {
|
||||
timeago.register('time-locale', timeLocale);
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleRedirectToURL = useCallback(() => {
|
||||
dispatch(markNotificationsAsRead(notification.id));
|
||||
window.open(notification.contentUrl, '_blank');
|
||||
}, [notification]);
|
||||
const handleMarkAsRead = useCallback(() => {
|
||||
dispatch(markNotificationsAsRead(id));
|
||||
}, [dispatch, id]);
|
||||
|
||||
const handleMarkAllAsRead = useCallback(() => {
|
||||
dispatch(markNotificationsAsRead(notification.id));
|
||||
}, [notification.id]);
|
||||
|
||||
const iconComponent = getIconByType(notification.type);
|
||||
const iconComponent = getIconByType(type);
|
||||
|
||||
return (
|
||||
<div className="d-flex mb-2 align-items-center">
|
||||
<Link className="d-flex mb-2 align-items-center text-decoration-none" to={contentUrl} onClick={handleMarkAsRead}>
|
||||
<Icon
|
||||
src={iconComponent && iconComponent.icon}
|
||||
style={{ height: '23.33px', width: '23.33px' }}
|
||||
@@ -38,33 +32,39 @@ const NotificationRowItem = ({ notification }) => {
|
||||
/>
|
||||
<div className="d-flex w-100">
|
||||
<div className="d-flex align-items-center w-100">
|
||||
<div className="py-2 w-100 px-0 cursor-pointer" onClick={handleRedirectToURL}>
|
||||
<div className="py-2 w-100 px-0 cursor-pointer">
|
||||
<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 }}
|
||||
dangerouslySetInnerHTML={{ __html: 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>{courseName}</span>
|
||||
<span className="text-light-700 px-1.5">{intl.formatMessage(messages.fullStop)}</span>
|
||||
<span>{timeago.format(notification?.createdAt, 'time-locale')}</span>
|
||||
<span>{timeago.format(createdAt, 'time-locale')}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!notification.lastRead && (
|
||||
<div className="d-flex py-1.5 px-1.5 ml-2 cursor-pointer" onClick={handleMarkAllAsRead}>
|
||||
{!lastRead && (
|
||||
<div className="d-flex py-1.5 px-1.5 ml-2 cursor-pointer">
|
||||
<span className="bg-brand-500 rounded unread" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
NotificationRowItem.propTypes = {
|
||||
notification: PropTypes.object.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
contentUrl: PropTypes.string.isRequired,
|
||||
content: PropTypes.node.isRequired,
|
||||
courseName: PropTypes.string.isRequired,
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
lastRead: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(NotificationRowItem);
|
||||
|
||||
@@ -1,83 +1,78 @@
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
import React, { useCallback } from 'react';
|
||||
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 PropTypes from 'prop-types';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { messages } from './messages';
|
||||
import NotificationRowItem from './NotificationRowItem';
|
||||
import {
|
||||
getSelectedAppNotificationIds, getSelectedAppName, getNotificationsByIds, getPaginationData,
|
||||
getSelectedAppNotificationIds,
|
||||
getSelectedAppName,
|
||||
getNotificationsByIds,
|
||||
getPaginationData,
|
||||
} from './data/selectors';
|
||||
import { splitNotificationsByTime } from './utils';
|
||||
import { markAllNotificationsAsRead } from './data/thunks';
|
||||
|
||||
const NotificationSections = ({ handleLoadMoreNotification }) => {
|
||||
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 { today = [], earlier = [] } = splitNotificationsByTime(notifications);
|
||||
const dispatch = useDispatch();
|
||||
const { today = [], earlier = [] } = useMemo(
|
||||
() => splitNotificationsByTime(notifications),
|
||||
[notifications],
|
||||
);
|
||||
|
||||
const handleMarkAllAsRead = useCallback(() => {
|
||||
dispatch(markAllNotificationsAsRead(selectedAppName));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedAppName]);
|
||||
}, [dispatch, selectedAppName]);
|
||||
|
||||
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">
|
||||
<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}>
|
||||
{intl.formatMessage(messages.notificationMarkAsRead)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{items.map((notification) => (
|
||||
<NotificationRowItem
|
||||
key={notification.id}
|
||||
id={notification.id}
|
||||
type={notification.type}
|
||||
contentUrl={notification.contentUrl}
|
||||
content={notification.content}
|
||||
courseName={notification.courseName}
|
||||
createdAt={notification.createdAt}
|
||||
lastRead={notification.lastRead}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
notifications && (
|
||||
<div className="mt-4 px-4">
|
||||
<div className="d-flex flex-row justify-content-between pb-2">
|
||||
{today.length > 0 && (
|
||||
<>
|
||||
<span className="text-gray-500">
|
||||
{ intl.formatMessage(messages.notificationTodayHeading)}
|
||||
</span>
|
||||
{today.length + earlier.length > 0 && (
|
||||
<span className="text-info-500 line-height-24 cursor-pointer" onClick={handleMarkAllAsRead}>
|
||||
{intl.formatMessage(messages.notificationMarkAsRead)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{today.map(
|
||||
(notification) => <NotificationRowItem notification={notification} />,
|
||||
)}
|
||||
|
||||
<div className="d-flex flex-row justify-content-between pb-2">
|
||||
<span className="text-gray-500">
|
||||
{earlier.length > 0
|
||||
&& intl.formatMessage(messages.notificationEarlierHeading)}
|
||||
</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.map(
|
||||
(notification) => <NotificationRowItem notification={notification} />,
|
||||
)}
|
||||
{renderNotificationSection('today', today)}
|
||||
{renderNotificationSection('earlier', earlier)}
|
||||
{paginationData.currentPage < paginationData.numPages && (
|
||||
<Button
|
||||
variant="primary"
|
||||
className="w-100 bg-primary-500"
|
||||
onClick={handleLoadMoreNotification}
|
||||
>
|
||||
{intl.formatMessage(messages.loadMoreNotifications)}
|
||||
</Button>
|
||||
<Button variant="primary" className="w-100 bg-primary-500" onClick={() => {}}>
|
||||
{intl.formatMessage(messages.loadMoreNotifications)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
NotificationSections.propTypes = {
|
||||
handleLoadMoreNotification: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(NotificationSections);
|
||||
|
||||
@@ -8,53 +8,37 @@ import { getNotificationTabsCount, getSelectedAppName, getNotificationTabs } fro
|
||||
import { fetchNotificationList, markNotificationsAsSeen } from './data/thunks';
|
||||
|
||||
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(getSelectedAppName());
|
||||
const [activeTab, setActiveTab] = useState(selectedAppName);
|
||||
const [page, setPage] = useState(1);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchNotificationList({ appName: activeTab, page, pageSize: 10 }));
|
||||
dispatch(fetchNotificationList({ appName: selectedAppName, page, pageSize: 10 }));
|
||||
if (selectedAppName) { dispatch(markNotificationsAsSeen(selectedAppName)); }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeTab, page, selectedAppName]);
|
||||
}, [dispatch, page, selectedAppName]);
|
||||
|
||||
const handleActiveTab = useCallback((tab) => {
|
||||
setActiveTab(tab);
|
||||
setPage(1);
|
||||
const handleActiveTab = useCallback((appName) => {
|
||||
// dispatch(setSelectedAppName(appName));
|
||||
}, []);
|
||||
|
||||
const handleLoadMoreNotification = useCallback(() => {
|
||||
setPage(page + 1);
|
||||
}, [page]);
|
||||
|
||||
const tabArray = useMemo(() => notificationTabs?.map((option) => (
|
||||
const tabArray = useMemo(() => notificationTabs?.map((appName) => (
|
||||
<Tab
|
||||
eventKey={option}
|
||||
title={option}
|
||||
notification={notificationUnseenCounts[option]}
|
||||
key={appName}
|
||||
eventKey={appName}
|
||||
title={appName}
|
||||
notification={notificationUnseenCounts[appName]}
|
||||
tabClassName="pt-0 pb-2.5 px-2.5 d-flex flex-row align-items-center line-height-24 text-capitalize"
|
||||
>
|
||||
{option === selectedAppName && (
|
||||
<NotificationSections
|
||||
handleLoadMoreNotification={handleLoadMoreNotification}
|
||||
/>
|
||||
)}
|
||||
{appName === selectedAppName && (<NotificationSections />)}
|
||||
</Tab>
|
||||
)), [notificationUnseenCounts, handleLoadMoreNotification, selectedAppName, notificationTabs]);
|
||||
|
||||
// This code is used to replace More... text to More to match the UI
|
||||
const buttons = document.getElementsByClassName('dropdown-toggle');
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
buttons[i].firstChild.nodeValue = 'More';
|
||||
}
|
||||
)), [notificationUnseenCounts, selectedAppName, notificationTabs]);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
variant="tabs"
|
||||
defaultActiveKey={activeTab}
|
||||
defaultActiveKey={selectedAppName}
|
||||
onSelect={handleActiveTab}
|
||||
className="px-2.5"
|
||||
>
|
||||
|
||||
@@ -25,25 +25,22 @@ const Notifications = () => {
|
||||
const isOnMediumScreen = useIsOnMediumScreen();
|
||||
const isOnLargeScreen = useIsOnLargeScreen();
|
||||
|
||||
console.log('isOnMediumScreen', isOnMediumScreen);
|
||||
console.log('isOnLargeScreen', isOnLargeScreen);
|
||||
|
||||
const handleNotificationTray = useCallback((value) => {
|
||||
setShowNotificationTray(value);
|
||||
if (!value) { dispatch(resetNotificationState()); }
|
||||
const hideNotificationTray = useCallback(() => {
|
||||
setShowNotificationTray(prevState => !prevState);
|
||||
}, []);
|
||||
|
||||
const handleClickOutside = useCallback((event) => {
|
||||
if (popoverRef.current?.contains(event.target) !== true && buttonRef.current?.contains(event.target) !== true) {
|
||||
if (!popoverRef.current?.contains(event.target) && !buttonRef.current?.contains(event.target)) {
|
||||
setShowNotificationTray(false);
|
||||
dispatch(resetNotificationState());
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
dispatch(resetNotificationState());
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -80,20 +77,22 @@ const Notifications = () => {
|
||||
<IconButton
|
||||
isActive={showNotificationTray}
|
||||
alt="notification bell icon"
|
||||
onClick={() => { handleNotificationTray(!showNotificationTray); }}
|
||||
onClick={hideNotificationTray}
|
||||
src={NotificationsNone}
|
||||
iconAs={Icon}
|
||||
variant="light"
|
||||
iconClassNames="text-primary-500"
|
||||
className="ml-4 mr-1 my-3 notification-button"
|
||||
/>
|
||||
<Badge
|
||||
variant="danger"
|
||||
pill
|
||||
className="font-weight-normal px-1 font-size-9 notification-badge"
|
||||
>
|
||||
{ notificationCounts?.count > 0 && notificationCounts?.count}
|
||||
</Badge>
|
||||
{notificationCounts?.count > 0 && (
|
||||
<Badge
|
||||
pill
|
||||
variant="danger"
|
||||
className="font-weight-normal px-1 font-size-9 notification-badge"
|
||||
>
|
||||
{notificationCounts.count}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"content_url": "",
|
||||
"last_read": null,
|
||||
"last_seen": null,
|
||||
"created_at": "2023-06-01T00:36:11.979531Z"
|
||||
"created_at": "2023-06-05T00:36:11.979531Z"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
|
||||
@@ -125,34 +125,37 @@ $white: #fff;
|
||||
.font-size-18{
|
||||
font-size: 18px !important;
|
||||
}
|
||||
|
||||
.font-size-12{
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.font-size-9{
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.line-height-24{
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.line-height-20{
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.icon-size-20{
|
||||
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;
|
||||
@@ -160,7 +163,14 @@ $white: #fff;
|
||||
border: 2px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.notification-tray-container{
|
||||
.notification-tray-container {
|
||||
max-height: calc(100% - 68px);
|
||||
min-height: 1220px;
|
||||
|
||||
.popover {
|
||||
|
||||
}
|
||||
|
||||
&.medium-screen {
|
||||
min-width: 24.313rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user