diff --git a/src/Notifications/NotificationRowItem.jsx b/src/Notifications/NotificationRowItem.jsx index 3957d38..c66d356 100644 --- a/src/Notifications/NotificationRowItem.jsx +++ b/src/Notifications/NotificationRowItem.jsx @@ -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 ( -
+ { />
-
+
- {notification?.courseName} + {courseName} {intl.formatMessage(messages.fullStop)} - {timeago.format(notification?.createdAt, 'time-locale')} + {timeago.format(createdAt, 'time-locale')}
- {!notification.lastRead && ( -
+ {!lastRead && ( +
)}
-
+ ); }; 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); diff --git a/src/Notifications/NotificationSections.jsx b/src/Notifications/NotificationSections.jsx index b40f873..d9aa446 100644 --- a/src/Notifications/NotificationSections.jsx +++ b/src/Notifications/NotificationSections.jsx @@ -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 ( +
+
+ + {section === 'today' && intl.formatMessage(messages.notificationTodayHeading)} + {section === 'earlier' && intl.formatMessage(messages.notificationEarlierHeading)} + + {notifications?.length > 0 && (section === 'earlier' ? today.length === 0 : true) && ( + + {intl.formatMessage(messages.notificationMarkAsRead)} + + )} +
+ {items.map((notification) => ( + + ))} +
+ ); + }; return ( - notifications && (
-
- {today.length > 0 && ( - <> - - { intl.formatMessage(messages.notificationTodayHeading)} - - {today.length + earlier.length > 0 && ( - - {intl.formatMessage(messages.notificationMarkAsRead)} - - )} - - )} -
- {today.map( - (notification) => , - )} - -
- - {earlier.length > 0 - && intl.formatMessage(messages.notificationEarlierHeading)} - - {today.length + earlier.length > 0 && today.length === 0 && ( - - {intl.formatMessage(messages.notificationMarkAsRead)} - - )} -
- {earlier.map( - (notification) => , - )} + {renderNotificationSection('today', today)} + {renderNotificationSection('earlier', earlier)} {paginationData.currentPage < paginationData.numPages && ( - + )}
- ) ); }; -NotificationSections.propTypes = { - handleLoadMoreNotification: PropTypes.func.isRequired, -}; - export default React.memo(NotificationSections); diff --git a/src/Notifications/NotificationTabs.jsx b/src/Notifications/NotificationTabs.jsx index 9540be3..8468f5f 100644 --- a/src/Notifications/NotificationTabs.jsx +++ b/src/Notifications/NotificationTabs.jsx @@ -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) => ( - {option === selectedAppName && ( - - )} + {appName === 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'); - for (let i = 0; i < buttons.length; i++) { - buttons[i].firstChild.nodeValue = 'More'; - } + )), [notificationUnseenCounts, selectedAppName, notificationTabs]); return ( diff --git a/src/Notifications/Notifications.jsx b/src/Notifications/Notifications.jsx index 2a68268..5ebb9ef 100644 --- a/src/Notifications/Notifications.jsx +++ b/src/Notifications/Notifications.jsx @@ -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 = () => { { handleNotificationTray(!showNotificationTray); }} + onClick={hideNotificationTray} src={NotificationsNone} iconAs={Icon} variant="light" iconClassNames="text-primary-500" className="ml-4 mr-1 my-3 notification-button" /> - - { notificationCounts?.count > 0 && notificationCounts?.count} - + {notificationCounts?.count > 0 && ( + + {notificationCounts.count} + + )}
); diff --git a/src/Notifications/data/notifications.json b/src/Notifications/data/notifications.json index 581c786..87e4eb6 100644 --- a/src/Notifications/data/notifications.json +++ b/src/Notifications/data/notifications.json @@ -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, diff --git a/src/index.scss b/src/index.scss index e8c2f9c..d9a607e 100644 --- a/src/index.scss +++ b/src/index.scss @@ -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; }