Skip to content

Commit e0b060d

Browse files
committed
feat: remove username
remove username from header VAN-1804
1 parent 3b2a2bf commit e0b060d

File tree

10 files changed

+211
-10
lines changed

10 files changed

+211
-10
lines changed

README.rst

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Environment Variables
4949
* ``ACCOUNT_PROFILE_URL`` - The URL of the account profile page.
5050
* ``ACCOUNT_SETTINGS_URL`` - The URL of the account settings page.
5151
* ``AUTHN_MINIMAL_HEADER`` - A boolean flag which hides the main menu, user menu, and logged-out
52+
* ``ENABLE_HEADER_WITHOUT_USERNAME`` - A boolean flag which hides the username from the header
5253
menu items when truthy. This is intended to be used in micro-frontends like
5354
frontend-app-authentication in which these menus are considered distractions from the user's task.
5455

example/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ subscribe(APP_READY, () => {
2626
authenticatedUser: {
2727
userId: '123abc',
2828
username: 'testuser',
29+
name: 'test user',
2930
roles: [],
3031
administrator: false,
3132
},

src/DesktopHeader.jsx

+33
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ class DesktopHeader extends React.Component {
7373
}
7474

7575
renderUserMenu() {
76+
return (getConfig().ENABLE_HEADER_WITHOUT_USERNAME ? this.userMenuWithoutUsername() : this.userMenuWithUsername());
77+
}
78+
79+
userMenuWithUsername() {
7680
const {
7781
userMenu,
7882
avatar,
@@ -99,6 +103,33 @@ class DesktopHeader extends React.Component {
99103
);
100104
}
101105

106+
userMenuWithoutUsername() {
107+
const {
108+
userMenu,
109+
avatar,
110+
name,
111+
intl,
112+
} = this.props;
113+
114+
return (
115+
<Menu transitionClassName="menu-dropdown" transitionTimeout={250}>
116+
<MenuTrigger
117+
tag="button"
118+
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { name })}
119+
className="btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
120+
>
121+
<Avatar size="1.5em" src={avatar} alt="" className="mr-2" />
122+
<CaretIcon role="img" aria-hidden focusable="false" />
123+
</MenuTrigger>
124+
<MenuContent className="mb-0 dropdown-menu show dropdown-menu-right pin-right shadow py-2">
125+
{userMenu.map(({ type, href, content }) => (
126+
<a className={`dropdown-${type}`} key={`${type}-${content}`} href={href}>{content}</a>
127+
))}
128+
</MenuContent>
129+
</Menu>
130+
);
131+
}
132+
102133
renderLoggedOutItems() {
103134
const { loggedOutItems } = this.props;
104135

@@ -178,6 +209,7 @@ DesktopHeader.propTypes = {
178209
logoDestination: PropTypes.string,
179210
avatar: PropTypes.string,
180211
username: PropTypes.string,
212+
name: PropTypes.string,
181213
loggedIn: PropTypes.bool,
182214

183215
// i18n
@@ -207,6 +239,7 @@ DesktopHeader.defaultProps = {
207239
logoDestination: null,
208240
avatar: null,
209241
username: null,
242+
name: null,
210243
loggedIn: false,
211244
appMenu: null,
212245
};

src/Header.jsx

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ ensureConfig([
2727
subscribe(APP_CONFIG_INITIALIZED, () => {
2828
mergeConfig({
2929
AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER,
30+
ENABLE_HEADER_WITHOUT_USERNAME: !!process.env.ENABLE_HEADER_WITHOUT_USERNAME,
3031
}, 'Header additional config');
3132
});
3233

@@ -94,6 +95,7 @@ const Header = ({ intl }) => {
9495
logoDestination: `${config.LMS_BASE_URL}/dashboard`,
9596
loggedIn: authenticatedUser !== null,
9697
username: authenticatedUser !== null ? authenticatedUser.username : null,
98+
name: authenticatedUser !== null ? authenticatedUser.name : null,
9799
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
98100
mainMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : mainMenu,
99101
userMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : userMenu,

src/Header.test.jsx

+27-1
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@ describe('<Header />', () => {
3838
expect(wrapper.toJSON()).toMatchSnapshot();
3939
});
4040

41-
it('renders correctly for authenticated desktop', () => {
41+
it('renders correctly for authenticated desktop with username', () => {
4242
const contextValue = {
4343
authenticatedUser: {
4444
userId: 'abc123',
4545
username: 'edX',
46+
name: 'edX',
4647
roles: [],
4748
administrator: false,
4849
},
@@ -61,6 +62,30 @@ describe('<Header />', () => {
6162
expect(wrapper.toJSON()).toMatchSnapshot();
6263
});
6364

65+
it('renders correctly for authenticated desktop without username', () => {
66+
const contextValue = {
67+
authenticatedUser: {
68+
userId: 'abc123',
69+
name: 'edX',
70+
roles: [],
71+
administrator: false,
72+
},
73+
config: {
74+
LMS_BASE_URL: process.env.LMS_BASE_URL,
75+
SITE_NAME: process.env.SITE_NAME,
76+
LOGIN_URL: process.env.LOGIN_URL,
77+
LOGOUT_URL: process.env.LOGOUT_URL,
78+
LOGO_URL: process.env.LOGO_URL,
79+
ENABLE_HEADER_WITHOUT_USERNAME: true,
80+
},
81+
};
82+
const component = <HeaderComponent width={{ width: 1280 }} contextValue={contextValue} />;
83+
84+
const wrapper = TestRenderer.create(component);
85+
86+
expect(wrapper.toJSON()).toMatchSnapshot();
87+
});
88+
6489
it('renders correctly for anonymous mobile', () => {
6590
const contextValue = {
6691
authenticatedUser: null,
@@ -84,6 +109,7 @@ describe('<Header />', () => {
84109
authenticatedUser: {
85110
userId: 'abc123',
86111
username: 'edX',
112+
name: 'edX',
87113
roles: [],
88114
administrator: false,
89115
},

src/__snapshots__/Header.test.jsx.snap

+109-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ exports[`<Header /> renders correctly for anonymous mobile 1`] = `
196196
</header>
197197
`;
198198

199-
exports[`<Header /> renders correctly for authenticated desktop 1`] = `
199+
exports[`<Header /> renders correctly for authenticated desktop with username 1`] = `
200200
<header
201201
className="site-header-desktop"
202202
>
@@ -305,6 +305,114 @@ exports[`<Header /> renders correctly for authenticated desktop 1`] = `
305305
</header>
306306
`;
307307

308+
exports[`<Header /> renders correctly for authenticated desktop without username 1`] = `
309+
<header
310+
className="site-header-desktop"
311+
>
312+
<a
313+
className="nav-skip sr-only sr-only-focusable"
314+
href="#main"
315+
>
316+
Skip to main content
317+
</a>
318+
<div
319+
className="container-fluid null"
320+
>
321+
<div
322+
className="nav-container position-relative d-flex align-items-center"
323+
>
324+
<a
325+
className="logo"
326+
href="http://localhost:18000/dashboard"
327+
>
328+
<img
329+
alt="edX"
330+
className="d-block"
331+
src="https://edx-cdn.org/v3/default/logo.svg"
332+
/>
333+
</a>
334+
<nav
335+
aria-label="Main"
336+
className="nav main-nav"
337+
>
338+
<a
339+
className="nav-link"
340+
href="http://localhost:18000/dashboard"
341+
>
342+
Courses
343+
</a>
344+
</nav>
345+
<nav
346+
aria-label="Secondary"
347+
className="nav secondary-menu-container align-items-center ml-auto"
348+
>
349+
<div
350+
className="menu null"
351+
onKeyDown={[Function]}
352+
onMouseEnter={[Function]}
353+
onMouseLeave={[Function]}
354+
>
355+
<button
356+
aria-expanded={false}
357+
aria-haspopup="menu"
358+
aria-label="Account menu for "
359+
className="menu-trigger btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
360+
onClick={[Function]}
361+
>
362+
<span
363+
className="avatar overflow-hidden d-inline-flex rounded-circle mr-2"
364+
style={
365+
Object {
366+
"height": "1.5em",
367+
"width": "1.5em",
368+
}
369+
}
370+
>
371+
<svg
372+
aria-hidden={true}
373+
focusable="false"
374+
height="24px"
375+
role="img"
376+
style={
377+
Object {
378+
"height": "1.5em",
379+
"width": "1.5em",
380+
}
381+
}
382+
version="1.1"
383+
viewBox="0 0 24 24"
384+
width="24px"
385+
>
386+
<path
387+
d="M4.10255106,18.1351061 C4.7170266,16.0581859 8.01891846,14.4720277 12,14.4720277 C15.9810815,14.4720277 19.2829734,16.0581859 19.8974489,18.1351061 C21.215206,16.4412566 22,14.3122775 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,14.3122775 2.78479405,16.4412566 4.10255106,18.1351061 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z M12,13 C9.790861,13 8,11.209139 8,9 C8,6.790861 9.790861,5 12,5 C14.209139,5 16,6.790861 16,9 C16,11.209139 14.209139,13 12,13 Z"
388+
fill="currentColor"
389+
/>
390+
</svg>
391+
</span>
392+
393+
<svg
394+
aria-hidden={true}
395+
focusable="false"
396+
height="16px"
397+
role="img"
398+
version="1.1"
399+
viewBox="0 0 16 16"
400+
width="16px"
401+
>
402+
<path
403+
d="M7,4 L7,8 L11,8 L11,10 L5,10 L5,4 L7,4 Z"
404+
fill="currentColor"
405+
transform="translate(8.000000, 7.000000) rotate(-45.000000) translate(-8.000000, -7.000000) "
406+
/>
407+
</svg>
408+
</button>
409+
</div>
410+
</nav>
411+
</div>
412+
</div>
413+
</header>
414+
`;
415+
308416
exports[`<Header /> renders correctly for authenticated mobile 1`] = `
309417
<header
310418
aria-label="Main"

src/learning-header/AuthenticatedUserDropdown.jsx

+15-7
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,36 @@ import PropTypes from 'prop-types';
44
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
55
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
66
import { getConfig } from '@edx/frontend-platform';
7+
import { Avatar } from '@openedx/paragon';
78
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
89
import { Dropdown } from '@openedx/paragon';
910

1011
import messages from './messages';
1112

12-
const AuthenticatedUserDropdown = ({ intl, username }) => {
13+
const AuthenticatedUserDropdown = ({ intl, username, avatar }) => {
1314
const dashboardMenuItem = (
1415
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}>
1516
{intl.formatMessage(messages.dashboard)}
1617
</Dropdown.Item>
1718
);
1819

20+
const showDropdownToggle = (
21+
<Dropdown.Toggle variant="outline-primary">
22+
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
23+
{!getConfig().ENABLE_HEADER_WITHOUT_USERNAME ? (
24+
<span data-hj-suppress className="d-none d-md-inline" data-testid="username">
25+
{username}
26+
</span>
27+
) : <Avatar size="sm" src={avatar} alt="" className="mr-2" />}
28+
</Dropdown.Toggle>
29+
);
30+
console.log("ENABLE_HEADER_WITHOUT_USERNAME", getConfig().ENABLE_HEADER_WITHOUT_USERNAME)
31+
1932
return (
2033
<>
2134
<a className="text-gray-700" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
2235
<Dropdown className="user-dropdown ml-3">
23-
<Dropdown.Toggle variant="outline-primary">
24-
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
25-
<span data-hj-suppress className="d-none d-md-inline">
26-
{username}
27-
</span>
28-
</Dropdown.Toggle>
36+
{showDropdownToggle}
2937
<Dropdown.Menu className="dropdown-menu-right">
3038
{dashboardMenuItem}
3139
<Dropdown.Item href={`${getConfig().ACCOUNT_PROFILE_URL}/u/${username}`}>

src/learning-header/LearningHeader.test.jsx

+13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { getConfig, mergeConfig } from '@edx/frontend-platform';
23
import {
34
authenticatedUser, initializeMockApp, render, screen,
45
} from '../setupTest';
@@ -15,6 +16,18 @@ describe('Header', () => {
1516
expect(screen.getByText(authenticatedUser.username)).toBeInTheDocument();
1617
});
1718

19+
it('displays user button without username', () => {
20+
const config = getConfig();
21+
mergeConfig({
22+
...config,
23+
ENABLE_HEADER_WITHOUT_USERNAME: true,
24+
});
25+
26+
const { queryByText } = render(<Header />);
27+
const userName = queryByText(config.authenticatedUser.username);
28+
expect(userName).not.toBeInTheDocument();
29+
});
30+
1831
it('displays course data', () => {
1932
const courseData = {
2033
courseOrg: 'course-org',

src/studio-header/StudioHeader.test.jsx

+7
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ describe('Header', () => {
128128
expect(avatarIcon).toBeVisible();
129129
});
130130

131+
it.only('user menu should not contain username', async () => {
132+
const newProps = { ...props, ENABLE_HEADER_WITHOUT_USERNAME: true };
133+
const { container } = render(<RootWrapper {...newProps} />);
134+
const userMenue = container.querySelector('#user-dropdown-menu');
135+
expect(userMenue.textContent).toContain('');
136+
});
137+
131138
it('should hide nav items if prop isHiddenMainMenu true', async () => {
132139
const initialProps = { ...props, isHiddenMainMenu: true };
133140
const { queryByTestId } = render(<RootWrapper {...initialProps} />);

src/studio-header/UserMenu.jsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
4+
import { getConfig } from '@edx/frontend-platform';
45
import {
56
Avatar,
67
} from '@openedx/paragon';
@@ -32,7 +33,8 @@ const UserMenu = ({
3233
data-testid="avatar-icon"
3334
/>
3435
);
35-
const title = isMobile ? avatar : <>{avatar}{username}</>;
36+
const showUsername = !getConfig().ENABLE_HEADER_WITHOUT_USERNAME;
37+
const title = isMobile ? avatar : <>{avatar}{showUsername && username}</>;
3638

3739
return (
3840
<NavDropdownMenu

0 commit comments

Comments
 (0)