refactor: code and style modifications

This commit is contained in:
Awais Ansari
2023-06-05 17:52:52 +05:00
parent 18a6840037
commit 78a40d47c1
6 changed files with 121 additions and 133 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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"
>

View File

@@ -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>
);

View File

@@ -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,

View File

@@ -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;
}