diff --git a/src/Notifications/NotificationRowItem.jsx b/src/Notifications/NotificationRowItem.jsx index ef3bcb3..3957d38 100644 --- a/src/Notifications/NotificationRowItem.jsx +++ b/src/Notifications/NotificationRowItem.jsx @@ -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 (
SCM_Lead posts Hello and welcome to SC0x!
", "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": "MITx_Learner asked What grade does a student need to get in order to pass the course and earn a certificate?
", "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": "SCM_Lead posts Hello and welcome to SC0x!
", "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": "MITx_Learner responded Can't find linear regression in section 3 review
", "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": "MITx_Learner commented on MITx_Expert's response on a post your following Can't find linear regression in section 3 review
", "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": "MITx_Learner commented Examples of quadratic equations in supply chains
", "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": "MITx_Expert answered Examples of quadratic equations in supply chains
", "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": "MITx_Learner commented Examples of quadratic equations in supply chains
", "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": "MITx_Learner commented on MITx_Expert'swhat grade does a student need to get in order to pass the course and earn a certificate?
", "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": "MITx_Learner commented on your response in Convexity of f(x)=1/x , x>1
", + "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": "SCM_Lead’s response has been marked as answer in your post Quiz in section 3 - Please explain the F-Significance value
", + "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": "Your response has been endorsed in Quiz in section 3 - Please explain the F-Significance value
", + "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": "MITx Learner’s post has been reported “Here are the exam answers. Question 1 - CSA stands for Compliance Safety Ac...”
", + "course_name": "Supply Chain Analytics", + "content_url": "", "last_read": null, "last_seen": null, "created_at": "2023-06-01T00:36:11.979531Z" diff --git a/src/Notifications/data/selectors.js b/src/Notifications/data/selectors.js index 295dfc1..ee9eb0e 100644 --- a/src/Notifications/data/selectors.js +++ b/src/Notifications/data/selectors.js @@ -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; diff --git a/src/Notifications/data/slice.js b/src/Notifications/data/slice.js index 7d92d4d..7e6373b 100644 --- a/src/Notifications/data/slice.js +++ b/src/Notifications/data/slice.js @@ -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; diff --git a/src/Notifications/data/thunks.js b/src/Notifications/data/thunks.js index 85bfb5a..a4ff8ac 100644 --- a/src/Notifications/data/thunks.js +++ b/src/Notifications/data/thunks.js @@ -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()); } +); diff --git a/src/Notifications/messages.js b/src/Notifications/messages.js index 62de292..40def3f 100644 --- a/src/Notifications/messages.js +++ b/src/Notifications/messages.js @@ -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: '•', diff --git a/src/Notifications/utils.js b/src/Notifications/utils.js index dea9f30..c5a5655 100644 --- a/src/Notifications/utils.js +++ b/src/Notifications/utils.js @@ -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; }; diff --git a/src/index.scss b/src/index.scss index d6689f9..0ad86fc 100644 --- a/src/index.scss +++ b/src/index.scss @@ -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; diff --git a/src/learning-header/AuthenticatedUserDropdown.jsx b/src/learning-header/AuthenticatedUserDropdown.jsx index a720516..05f1540 100644 --- a/src/learning-header/AuthenticatedUserDropdown.jsx +++ b/src/learning-header/AuthenticatedUserDropdown.jsx @@ -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 = (