Skip to content

Commit ae02ba4

Browse files
feat: added notification tray icon for learner dashboard (#608)
* feat: added notification tray icon for learner dashboard * refactor: fixed suggestions and removed showTray state varaible
1 parent 01b28ad commit ae02ba4

File tree

8 files changed

+91
-21
lines changed

8 files changed

+91
-21
lines changed

src/DesktopHeader.jsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import UserMenuGroupSlot from './plugin-slots/UserMenuGroupSlot';
1010
import UserMenuItem from './common/UserMenuItem';
1111
import { Menu, MenuTrigger, MenuContent } from './Menu';
1212
import { LinkedLogo, Logo } from './Logo';
13+
import withNotifications from './Notification/withNotifications';
14+
import Notifications from './Notification';
1315

1416
// i18n
1517
import messages from './Header.messages';
@@ -153,6 +155,7 @@ class DesktopHeader extends React.Component {
153155
logoDestination,
154156
loggedIn,
155157
intl,
158+
notificationAppData,
156159
} = this.props;
157160
const logoProps = { src: logo, alt: logoAltText, href: logoDestination };
158161
const logoClasses = getConfig().AUTHN_MINIMAL_HEADER ? 'mw-100' : null;
@@ -177,6 +180,8 @@ class DesktopHeader extends React.Component {
177180
? (
178181
<>
179182
{this.renderSecondaryMenu()}
183+
{notificationAppData?.showNotificationsTray
184+
&& <Notifications notificationAppData={notificationAppData} showLeftMargin={false} />}
180185
{this.renderUserMenu()}
181186
</>
182187
) : this.renderLoggedOutItems()}
@@ -220,7 +225,19 @@ DesktopHeader.propTypes = {
220225
name: PropTypes.string,
221226
email: PropTypes.string,
222227
loggedIn: PropTypes.bool,
223-
228+
notificationAppData: PropTypes.shape({
229+
apps: PropTypes.objectOf(
230+
PropTypes.arrayOf(PropTypes.string),
231+
).isRequired,
232+
appsId: PropTypes.arrayOf(PropTypes.string).isRequired,
233+
isNewNotificationViewEnabled: PropTypes.bool.isRequired,
234+
notificationExpiryDays: PropTypes.number.isRequired,
235+
notificationStatus: PropTypes.string.isRequired,
236+
showNotificationsTray: PropTypes.bool.isRequired,
237+
tabsCount: PropTypes.shape({
238+
count: PropTypes.number.isRequired,
239+
}).isRequired,
240+
}),
224241
// i18n
225242
intl: intlShape.isRequired,
226243
};
@@ -237,6 +254,15 @@ DesktopHeader.defaultProps = {
237254
name: '',
238255
email: '',
239256
loggedIn: false,
257+
notificationAppData: {
258+
apps: { },
259+
tabsCount: { },
260+
appsId: [],
261+
isNewNotificationViewEnabled: false,
262+
notificationExpiryDays: 0,
263+
notificationStatus: '',
264+
showNotificationsTray: false,
265+
},
240266
};
241267

242-
export default injectIntl(DesktopHeader);
268+
export default injectIntl(withNotifications(DesktopHeader));

src/Header.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@ ensureConfig([
2626
'MARKETING_SITE_BASE_URL',
2727
'ORDER_HISTORY_URL',
2828
'LOGO_URL',
29+
'ACCOUNT_SETTINGS_URL',
30+
'NOTIFICATION_FEEDBACK_URL',
2931
], 'Header component');
3032

3133
subscribe(APP_CONFIG_INITIALIZED, () => {
3234
mergeConfig({
3335
MINIMAL_HEADER: !!process.env.MINIMAL_HEADER,
3436
ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME,
3537
AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER,
38+
ACCOUNT_SETTINGS_URL: process.env.ACCOUNT_SETTINGS_URL,
39+
NOTIFICATION_FEEDBACK_URL: process.env.NOTIFICATION_FEEDBACK_URL,
3640
}, 'Header additional config');
3741
});
3842

src/Header.test.jsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import { useEnterpriseConfig } from '@edx/frontend-enterprise-utils';
77
import { AppContext } from '@edx/frontend-platform/react';
88
import { getConfig } from '@edx/frontend-platform';
99
import { Context as ResponsiveContext } from 'react-responsive';
10+
import { MemoryRouter } from 'react-router-dom';
1011

1112
import { fireEvent, render, screen } from '@testing-library/react';
1213
import Header from './index';
14+
import { initializeMockApp } from './setupTest';
1315

1416
jest.mock('@edx/frontend-platform');
1517
jest.mock('@edx/frontend-enterprise-utils');
@@ -26,22 +28,29 @@ const APP_CONTEXT_CONFIG = {
2628
};
2729

2830
const HeaderContext = ({ width, contextValue }) => (
29-
<ResponsiveContext.Provider value={width}>
30-
<IntlProvider locale="en" messages={{}}>
31-
<AppContext.Provider
32-
value={contextValue}
33-
>
34-
<Header />
35-
</AppContext.Provider>
36-
</IntlProvider>
37-
</ResponsiveContext.Provider>
31+
<MemoryRouter>
32+
<ResponsiveContext.Provider value={width}>
33+
<IntlProvider locale="en" messages={{}}>
34+
<AppContext.Provider
35+
value={contextValue}
36+
>
37+
<Header />
38+
</AppContext.Provider>
39+
</IntlProvider>
40+
</ResponsiveContext.Provider>
41+
</MemoryRouter>
3842
);
3943

4044
describe('<Header />', () => {
4145
beforeEach(() => {
4246
useEnterpriseConfig.mockReturnValue({});
4347
});
4448

49+
beforeAll(async () => {
50+
// We need to mock AuthService to implicitly use `getAuthenticatedUser` within `AppContext.Provider`.
51+
await initializeMockApp();
52+
});
53+
4554
const mockUseEnterpriseConfig = () => {
4655
useEnterpriseConfig.mockReturnValue({
4756
enterpriseLearnerPortalLink: {

src/MobileHeader.jsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import UserMenuGroupItemSlot from './plugin-slots/UserMenuGroupItemSlot';
1010
import { Menu, MenuTrigger, MenuContent } from './Menu';
1111
import { LinkedLogo, Logo } from './Logo';
1212
import UserMenuItem from './common/UserMenuItem';
13-
13+
import withNotifications from './Notification/withNotifications';
14+
import Notifications from './Notification';
1415
// i18n
1516
import messages from './Header.messages';
1617

@@ -135,6 +136,7 @@ class MobileHeader extends React.Component {
135136
mainMenu,
136137
userMenu,
137138
loggedOutItems,
139+
notificationAppData,
138140
} = this.props;
139141
const logoProps = { src: logo, alt: logoAltText, href: logoDestination };
140142
const stickyClassName = stickyOnMobile ? 'sticky-top' : '';
@@ -173,6 +175,7 @@ class MobileHeader extends React.Component {
173175
</div>
174176
{userMenu.length > 0 || loggedOutItems.length > 0 ? (
175177
<div className="w-100 d-flex justify-content-end align-items-center">
178+
{notificationAppData?.showNotificationsTray && <Notifications notificationAppData={notificationAppData} />}
176179
<Menu tag="nav" aria-label={intl.formatMessage(messages['header.label.secondary.nav'])} className="position-static">
177180
<MenuTrigger
178181
tag={AvatarButton}
@@ -227,7 +230,19 @@ MobileHeader.propTypes = {
227230
email: PropTypes.string,
228231
loggedIn: PropTypes.bool,
229232
stickyOnMobile: PropTypes.bool,
230-
233+
notificationAppData: PropTypes.shape({
234+
apps: PropTypes.objectOf(
235+
PropTypes.arrayOf(PropTypes.string),
236+
).isRequired,
237+
appsId: PropTypes.arrayOf(PropTypes.string).isRequired,
238+
isNewNotificationViewEnabled: PropTypes.bool.isRequired,
239+
notificationExpiryDays: PropTypes.number.isRequired,
240+
notificationStatus: PropTypes.string.isRequired,
241+
showNotificationsTray: PropTypes.bool.isRequired,
242+
tabsCount: PropTypes.shape({
243+
count: PropTypes.number.isRequired,
244+
}).isRequired,
245+
}),
231246
// i18n
232247
intl: intlShape.isRequired,
233248
};
@@ -245,7 +260,15 @@ MobileHeader.defaultProps = {
245260
email: '',
246261
loggedIn: false,
247262
stickyOnMobile: true,
248-
263+
notificationAppData: {
264+
apps: {},
265+
tabsCount: {},
266+
appsId: [],
267+
isNewNotificationViewEnabled: false,
268+
notificationExpiryDays: 0,
269+
notificationStatus: '',
270+
showNotificationsTray: false,
271+
},
249272
};
250273

251-
export default injectIntl(MobileHeader);
274+
export default injectIntl(withNotifications(MobileHeader));

src/Notification/NotificationSections.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const NotificationSections = () => {
5656
variant="link"
5757
className="small line-height-10 text-decoration-none p-0 border-0 text-info-500"
5858
onClick={handleMarkAllAsRead}
59+
size="sm"
5960
data-testid="mark-all-read"
6061
>
6162
{intl.formatMessage(messages.notificationMarkAsRead)}

src/Notification/data/hook.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,17 +177,15 @@ export function useNotification() {
177177

178178
export function useAppNotifications() {
179179
const { authenticatedUser } = useContext(AppContext);
180-
const [showTray, setShowTray] = useState();
181180
const [isNewNotificationView, setIsNewNotificationView] = useState(false);
182181
const [notificationAppData, setNotificationAppData] = useState();
183182
const { fetchAppsNotificationCount } = useNotification();
184183
const location = useLocation();
185184

186185
const fetchNotificationData = useCallback(async () => {
187186
const data = await fetchAppsNotificationCount();
188-
const { showNotificationsTray, isNewNotificationViewEnabled } = data;
187+
const { isNewNotificationViewEnabled } = data;
189188

190-
setShowTray(showNotificationsTray);
191189
setIsNewNotificationView(isNewNotificationViewEnabled);
192190
setNotificationAppData(data);
193191
}, [fetchAppsNotificationCount]);
@@ -202,7 +200,6 @@ export function useAppNotifications() {
202200
}, [fetchNotificationData, authenticatedUser, location.pathname]);
203201

204202
return {
205-
showTray,
206203
isNewNotificationView,
207204
notificationAppData,
208205
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
3+
import { useAppNotifications } from './data/hook';
4+
5+
export default function withNotifications(Component) {
6+
return function WrapperComponent(props) {
7+
const { notificationAppData } = useAppNotifications();
8+
return <Component {...props} notificationAppData={notificationAppData} />;
9+
};
10+
}

src/learning-header/AuthenticatedUser.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ const AuthenticatedUser = ({
3131
enterpriseLearnerPortalLink,
3232
}) => {
3333
const { authenticatedUser } = useContext(AppContext);
34-
const { showTray, notificationAppData } = useAppNotifications();
34+
const { notificationAppData } = useAppNotifications();
3535

3636
return (
3737
<BaseAuthenticatedUser>
38-
{showTray && <Notifications notificationAppData={notificationAppData} />}
38+
{notificationAppData?.showNotificationsTray && <Notifications notificationAppData={notificationAppData} />}
3939
{showUserDropdown && (
4040
<AuthenticatedUserDropdown
4141
enterpriseLearnerPortalLink={enterpriseLearnerPortalLink}

0 commit comments

Comments
 (0)