Skip to content

Commit 2e76a03

Browse files
feat: show user email & name in user menu dropdown (#558)
VAN-1851
1 parent 2bba7f5 commit 2e76a03

18 files changed

Lines changed: 184 additions & 85 deletions

example/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ subscribe(APP_READY, () => {
2626
authenticatedUser: {
2727
userId: '123abc',
2828
username: 'testuser',
29-
name: 'test user',
29+
name: 'Test User',
30+
email: 'test@example.com',
3031
roles: [],
3132
administrator: false,
3233
},
@@ -38,8 +39,9 @@ subscribe(APP_READY, () => {
3839
<AppContext.Provider value={{
3940
authenticatedUser: {
4041
userId: '123abc',
41-
name: 'test name',
4242
username: 'testuser',
43+
name: 'Test User',
44+
email: 'test@example.com',
4345
roles: [],
4446
administrator: false,
4547
},

src/DesktopHeader.jsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import React from 'react';
22
import PropTypes from 'prop-types';
33
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
44
import { getConfig } from '@edx/frontend-platform';
5+
import { AvatarButton, Dropdown } from '@openedx/paragon';
56

67
// Local Components
7-
import { AvatarButton, Dropdown } from '@openedx/paragon';
8+
import UserMenuItem from './common/UserMenuItem';
89
import { Menu, MenuTrigger, MenuContent } from './Menu';
910
import { LinkedLogo, Logo } from './Logo';
1011

@@ -58,12 +59,10 @@ class DesktopHeader extends React.Component {
5859
const {
5960
userMenu,
6061
avatar,
61-
username,
6262
name,
63+
email,
6364
intl,
6465
} = this.props;
65-
const hideUsername = getConfig().HIDE_USERNAME_FROM_HEADER;
66-
const usernameOrName = hideUsername ? name : username;
6766

6867
return (
6968
<Dropdown>
@@ -72,13 +71,16 @@ class DesktopHeader extends React.Component {
7271
as={AvatarButton}
7372
src={avatar}
7473
alt=""
75-
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { username: usernameOrName })}
74+
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { name })}
7675
data-hj-suppress
77-
>
78-
{!hideUsername && username}
79-
</Dropdown.Toggle>
80-
76+
/>
8177
<Dropdown.Menu alignRight>
78+
<Dropdown.Item key="user-info">
79+
<UserMenuItem
80+
name={name}
81+
email={email}
82+
/>
83+
</Dropdown.Item>
8284
{userMenu.map(({ type, href, content }) => (
8385
<Dropdown.Item key={`${type}-${content}`} href={href}>
8486
{content}
@@ -158,8 +160,8 @@ DesktopHeader.propTypes = {
158160
logoAltText: PropTypes.string,
159161
logoDestination: PropTypes.string,
160162
avatar: PropTypes.string,
161-
username: PropTypes.string,
162163
name: PropTypes.string,
164+
email: PropTypes.string,
163165
loggedIn: PropTypes.bool,
164166

165167
// i18n
@@ -174,8 +176,8 @@ DesktopHeader.defaultProps = {
174176
logoAltText: null,
175177
logoDestination: null,
176178
avatar: null,
177-
username: null,
178179
name: '',
180+
email: '',
179181
loggedIn: false,
180182
};
181183

src/Header.jsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ subscribe(APP_CONFIG_INITIALIZED, () => {
3232
MINIMAL_HEADER: !!process.env.MINIMAL_HEADER,
3333
ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME,
3434
AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER,
35-
// this flag is introduced to unblock for new release.
36-
// the flag would be removed in follow up PR
37-
HIDE_USERNAME_FROM_HEADER: !!process.env.HIDE_USERNAME_FROM_HEADER,
3835
}, 'Header additional config');
3936
});
4037

@@ -157,8 +154,8 @@ const Header = ({ intl }) => {
157154
siteName: 'edX',
158155
logoDestination: getConfig().MINIMAL_HEADER ? null : `${config.LMS_BASE_URL}/dashboard`,
159156
loggedIn: authenticatedUser !== null,
160-
username: authenticatedUser !== null ? authenticatedUser.username : null,
161-
name: authenticatedUser !== null ? authenticatedUser.name : null,
157+
name: authenticatedUser !== null ? authenticatedUser.name : '',
158+
email: authenticatedUser !== null ? authenticatedUser.email : '',
162159
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
163160
mainMenu: getConfig().MINIMAL_HEADER || getConfig().AUTHN_MINIMAL_HEADER ? [] : mainMenu,
164161
userMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : userMenu,

src/Header.messages.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ const messages = defineMessages({
7878
},
7979
'header.label.account.menu.for': {
8080
id: 'header.label.account.menu.for',
81-
defaultMessage: 'Account menu for {username}',
82-
description: 'The aria label for the account menu trigger when the username is displayed in it',
81+
defaultMessage: 'Account menu for {name}',
82+
description: 'The aria label for the account menu trigger',
8383
},
8484
'header.label.main.nav': {
8585
id: 'header.label.main.nav',

src/Header.test.jsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,27 @@ describe('<Header />', () => {
8787
expect(wrapper.toJSON()).toMatchSnapshot();
8888
});
8989

90+
it('displays user menu in dropdown', () => {
91+
const authenticatedUser = {
92+
userId: 'abc123',
93+
username: 'edX',
94+
name: 'edX',
95+
email: 'test@example.com',
96+
roles: [],
97+
administrator: false,
98+
};
99+
const contextValue = {
100+
authenticatedUser,
101+
config: APP_CONTEXT_CONFIG,
102+
};
103+
const component = <HeaderContext width={{ width: 1280 }} contextValue={contextValue} />;
104+
const wrapper = render(component);
105+
fireEvent.click(wrapper.container.querySelector('#menu-dropdown'));
106+
107+
expect(screen.getByText(authenticatedUser.name)).toBeInTheDocument();
108+
expect(screen.getByText(authenticatedUser.email)).toBeInTheDocument();
109+
});
110+
90111
it('renders correctly for authenticated users on desktop with or without learner portal links', async () => {
91112
const contextValue = {
92113
authenticatedUser: {
@@ -143,6 +164,7 @@ describe('<Header />', () => {
143164
authenticatedUser: {
144165
userId: 'abc123',
145166
username: 'edX',
167+
name: 'edX Test',
146168
roles: [],
147169
administrator: false,
148170
},
@@ -185,6 +207,7 @@ describe('<Header />', () => {
185207
authenticatedUser: {
186208
userId: 'abc123',
187209
username: 'edX',
210+
name: 'edX Test',
188211
roles: [],
189212
administrator: false,
190213
},

src/MobileHeader.jsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getConfig } from '@edx/frontend-platform';
77
import { AvatarButton } from '@openedx/paragon';
88
import { Menu, MenuTrigger, MenuContent } from './Menu';
99
import { LinkedLogo, Logo } from './Logo';
10+
import UserMenuItem from './common/UserMenuItem';
1011

1112
// i18n
1213
import messages from './Header.messages';
@@ -57,13 +58,18 @@ class MobileHeader extends React.Component {
5758
}
5859

5960
renderUserMenuItems() {
60-
const { userMenu } = this.props;
61-
62-
return userMenu.map(({ type, href, content }) => (
61+
const { userMenu, name, email } = this.props;
62+
const userInfoItem = (
63+
<li className="nav-item" key="user-info">
64+
<UserMenuItem name={name} email={email} />
65+
</li>
66+
);
67+
const userMenuItems = userMenu.map(({ type, href, content }) => (
6368
<li className="nav-item" key={`${type}-${content}`}>
6469
<a className="nav-link" href={href}>{content}</a>
6570
</li>
6671
));
72+
return [userInfoItem, ...userMenuItems];
6773
}
6874

6975
renderLoggedOutItems() {
@@ -88,7 +94,7 @@ class MobileHeader extends React.Component {
8894
logoDestination,
8995
loggedIn,
9096
avatar,
91-
username,
97+
name,
9298
stickyOnMobile,
9399
intl,
94100
mainMenu,
@@ -139,7 +145,7 @@ class MobileHeader extends React.Component {
139145
src={avatar}
140146
showLabel={false}
141147
>
142-
{username}
148+
{name}
143149
</MenuTrigger>
144150
<MenuContent tag="ul" className="nav flex-column pin-left pin-right border-top shadow py-2">
145151
{loggedIn ? this.renderUserMenuItems() : this.renderLoggedOutItems()}
@@ -172,7 +178,8 @@ MobileHeader.propTypes = {
172178
logoAltText: PropTypes.string,
173179
logoDestination: PropTypes.string,
174180
avatar: PropTypes.string,
175-
username: PropTypes.string,
181+
name: PropTypes.string,
182+
email: PropTypes.string,
176183
loggedIn: PropTypes.bool,
177184
stickyOnMobile: PropTypes.bool,
178185

@@ -188,7 +195,8 @@ MobileHeader.defaultProps = {
188195
logoAltText: null,
189196
logoDestination: null,
190197
avatar: null,
191-
username: null,
198+
name: '',
199+
email: '',
192200
loggedIn: false,
193201
stickyOnMobile: true,
194202

src/__snapshots__/Header.test.jsx.snap

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ exports[`<Header /> minimal renders correctly for authenticated users when minim
3737
alt=""
3838
aria-expanded={false}
3939
aria-haspopup={true}
40-
aria-label="Account menu for edX"
40+
aria-label="Account menu for edX Test"
4141
className="btn-avatar pgn__avatar-button-avatar pgn__avatar-button-avatar-md dropdown-toggle btn btn-tertiary btn-md"
4242
data-hj-suppress={true}
4343
disabled={false}
@@ -50,7 +50,6 @@ exports[`<Header /> minimal renders correctly for authenticated users when minim
5050
className="pgn__avatar pgn__avatar-sm"
5151
src="icon/mock/path"
5252
/>
53-
edX
5453
</button>
5554
</div>
5655
</nav>
@@ -180,7 +179,6 @@ exports[`<Header /> renders correctly for authenticated users on desktop 1`] = `
180179
className="pgn__avatar pgn__avatar-sm"
181180
src="icon/mock/path"
182181
/>
183-
edX
184182
</button>
185183
</div>
186184
</nav>
@@ -293,7 +291,7 @@ exports[`<Header /> renders correctly for authenticated users on mobile 1`] = `
293291
type="button"
294292
>
295293
<img
296-
alt="edX"
294+
alt="edX Test"
297295
className="pgn__avatar pgn__avatar-sm"
298296
src="icon/mock/path"
299297
/>
@@ -478,7 +476,7 @@ exports[`<Header /> renders correctly for unauthenticated users on mobile 1`] =
478476
type="button"
479477
>
480478
<img
481-
alt={null}
479+
alt=""
482480
className="pgn__avatar pgn__avatar-sm"
483481
src="icon/mock/path"
484482
/>

src/common/UserMenuItem.jsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { Avatar } from '@openedx/paragon';
5+
import { injectIntl } from '@edx/frontend-platform/i18n';
6+
7+
import './style.scss';
8+
9+
const UserMenuItem = ({ name, email }) => (
10+
<>
11+
<Avatar
12+
size="sm"
13+
className="mr-2"
14+
alt={name}
15+
data-testid="avatar-icon"
16+
/>
17+
<div className="text-left">
18+
{name && <span className="h5 d-block">{name}</span>}
19+
{email && <span className="small d-block">{email}</span>}
20+
</div>
21+
</>
22+
);
23+
24+
UserMenuItem.propTypes = {
25+
name: PropTypes.string,
26+
email: PropTypes.string,
27+
};
28+
29+
UserMenuItem.defaultProps = {
30+
name: '',
31+
email: '',
32+
};
33+
34+
export default injectIntl(UserMenuItem);

src/common/style.scss

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.dropdown-menu a:first-child {
2+
pointer-events: none;
3+
}
4+
5+
@media screen and (max-width: 768px) {
6+
.menu-content li:first-child {
7+
display: flex;
8+
align-items: center;
9+
padding: 0 16px;
10+
border-bottom: 1px solid #70828E;
11+
12+
a {
13+
display: flex;
14+
align-items: center;
15+
border-bottom: 1px solid #70828E !important;
16+
}
17+
}
18+
}

src/index.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,8 @@ $white: #fff;
119119
border-radius: $rounded-pill;
120120
}
121121
}
122+
123+
// bottom boarder of first child of user dropdown menu
124+
.dropdown-menu a:first-child {
125+
border-bottom: 1px solid #70828E !important;
126+
}

0 commit comments

Comments
 (0)