Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add user email and name in user dropdown menu #558

Merged
merged 1 commit into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ subscribe(APP_READY, () => {
authenticatedUser: {
userId: '123abc',
username: 'testuser',
name: 'test user',
name: 'Test User',
email: 'test@example.com',
roles: [],
administrator: false,
},
Expand All @@ -38,8 +39,9 @@ subscribe(APP_READY, () => {
<AppContext.Provider value={{
authenticatedUser: {
userId: '123abc',
name: 'test name',
username: 'testuser',
name: 'Test User',
email: 'test@example.com',
roles: [],
administrator: false,
},
Expand Down
24 changes: 13 additions & 11 deletions src/DesktopHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import { AvatarButton, Dropdown } from '@openedx/paragon';

// Local Components
import { AvatarButton, Dropdown } from '@openedx/paragon';
import UserMenuItem from './common/UserMenuItem';
import { Menu, MenuTrigger, MenuContent } from './Menu';
import { LinkedLogo, Logo } from './Logo';

Expand Down Expand Up @@ -58,12 +59,10 @@ class DesktopHeader extends React.Component {
const {
userMenu,
avatar,
username,
name,
email,
intl,
} = this.props;
const hideUsername = getConfig().HIDE_USERNAME_FROM_HEADER;
const usernameOrName = hideUsername ? name : username;

return (
<Dropdown>
Expand All @@ -72,13 +71,16 @@ class DesktopHeader extends React.Component {
as={AvatarButton}
src={avatar}
alt=""
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { username: usernameOrName })}
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { name })}
data-hj-suppress
>
{!hideUsername && username}
</Dropdown.Toggle>

/>
<Dropdown.Menu alignRight>
<Dropdown.Item key="user-info">
<UserMenuItem
name={name}
email={email}
/>
</Dropdown.Item>
{userMenu.map(({ type, href, content }) => (
<Dropdown.Item key={`${type}-${content}`} href={href}>
{content}
Expand Down Expand Up @@ -158,8 +160,8 @@ DesktopHeader.propTypes = {
logoAltText: PropTypes.string,
logoDestination: PropTypes.string,
avatar: PropTypes.string,
username: PropTypes.string,
name: PropTypes.string,
email: PropTypes.string,
loggedIn: PropTypes.bool,

// i18n
Expand All @@ -174,8 +176,8 @@ DesktopHeader.defaultProps = {
logoAltText: null,
logoDestination: null,
avatar: null,
username: null,
name: '',
email: '',
loggedIn: false,
};

Expand Down
7 changes: 2 additions & 5 deletions src/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ subscribe(APP_CONFIG_INITIALIZED, () => {
MINIMAL_HEADER: !!process.env.MINIMAL_HEADER,
ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME,
AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER,
// this flag is introduced to unblock for new release.
// the flag would be removed in follow up PR
HIDE_USERNAME_FROM_HEADER: !!process.env.HIDE_USERNAME_FROM_HEADER,
}, 'Header additional config');
});

Expand Down Expand Up @@ -157,8 +154,8 @@ const Header = ({ intl }) => {
siteName: 'edX',
logoDestination: getConfig().MINIMAL_HEADER ? null : `${config.LMS_BASE_URL}/dashboard`,
loggedIn: authenticatedUser !== null,
username: authenticatedUser !== null ? authenticatedUser.username : null,
name: authenticatedUser !== null ? authenticatedUser.name : null,
name: authenticatedUser !== null ? authenticatedUser.name : '',
syedsajjadkazmii marked this conversation as resolved.
Show resolved Hide resolved
email: authenticatedUser !== null ? authenticatedUser.email : '',
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
mainMenu: getConfig().MINIMAL_HEADER || getConfig().AUTHN_MINIMAL_HEADER ? [] : mainMenu,
userMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : userMenu,
Expand Down
4 changes: 2 additions & 2 deletions src/Header.messages.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ const messages = defineMessages({
},
'header.label.account.menu.for': {
id: 'header.label.account.menu.for',
defaultMessage: 'Account menu for {username}',
description: 'The aria label for the account menu trigger when the username is displayed in it',
defaultMessage: 'Account menu for {name}',
description: 'The aria label for the account menu trigger',
},
'header.label.main.nav': {
id: 'header.label.main.nav',
Expand Down
23 changes: 23 additions & 0 deletions src/Header.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,27 @@ describe('<Header />', () => {
expect(wrapper.toJSON()).toMatchSnapshot();
});

it('displays user menu in dropdown', () => {
const authenticatedUser = {
userId: 'abc123',
username: 'edX',
name: 'edX',
email: 'test@example.com',
roles: [],
administrator: false,
};
const contextValue = {
authenticatedUser,
config: APP_CONTEXT_CONFIG,
};
const component = <HeaderContext width={{ width: 1280 }} contextValue={contextValue} />;
const wrapper = render(component);
fireEvent.click(wrapper.container.querySelector('#menu-dropdown'));

expect(screen.getByText(authenticatedUser.name)).toBeInTheDocument();
expect(screen.getByText(authenticatedUser.email)).toBeInTheDocument();
});

it('renders correctly for authenticated users on desktop with or without learner portal links', async () => {
const contextValue = {
authenticatedUser: {
Expand Down Expand Up @@ -143,6 +164,7 @@ describe('<Header />', () => {
authenticatedUser: {
userId: 'abc123',
username: 'edX',
name: 'edX Test',
roles: [],
administrator: false,
},
Expand Down Expand Up @@ -185,6 +207,7 @@ describe('<Header />', () => {
authenticatedUser: {
userId: 'abc123',
username: 'edX',
name: 'edX Test',
roles: [],
administrator: false,
},
Expand Down
22 changes: 15 additions & 7 deletions src/MobileHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getConfig } from '@edx/frontend-platform';
import { AvatarButton } from '@openedx/paragon';
import { Menu, MenuTrigger, MenuContent } from './Menu';
import { LinkedLogo, Logo } from './Logo';
import UserMenuItem from './common/UserMenuItem';

// i18n
import messages from './Header.messages';
Expand Down Expand Up @@ -57,13 +58,18 @@ class MobileHeader extends React.Component {
}

renderUserMenuItems() {
const { userMenu } = this.props;

return userMenu.map(({ type, href, content }) => (
const { userMenu, name, email } = this.props;
const userInfoItem = (
<li className="nav-item" key="user-info">
<UserMenuItem name={name} email={email} />
</li>
);
const userMenuItems = userMenu.map(({ type, href, content }) => (
<li className="nav-item" key={`${type}-${content}`}>
<a className="nav-link" href={href}>{content}</a>
</li>
));
return [userInfoItem, ...userMenuItems];
}

renderLoggedOutItems() {
Expand All @@ -88,7 +94,7 @@ class MobileHeader extends React.Component {
logoDestination,
loggedIn,
avatar,
username,
name,
stickyOnMobile,
intl,
mainMenu,
Expand Down Expand Up @@ -139,7 +145,7 @@ class MobileHeader extends React.Component {
src={avatar}
showLabel={false}
>
{username}
{name}
</MenuTrigger>
<MenuContent tag="ul" className="nav flex-column pin-left pin-right border-top shadow py-2">
{loggedIn ? this.renderUserMenuItems() : this.renderLoggedOutItems()}
Expand Down Expand Up @@ -172,7 +178,8 @@ MobileHeader.propTypes = {
logoAltText: PropTypes.string,
logoDestination: PropTypes.string,
avatar: PropTypes.string,
username: PropTypes.string,
name: PropTypes.string,
email: PropTypes.string,
loggedIn: PropTypes.bool,
stickyOnMobile: PropTypes.bool,

Expand All @@ -188,7 +195,8 @@ MobileHeader.defaultProps = {
logoAltText: null,
logoDestination: null,
avatar: null,
username: null,
name: '',
email: '',
loggedIn: false,
stickyOnMobile: true,

Expand Down
8 changes: 3 additions & 5 deletions src/__snapshots__/Header.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ exports[`<Header /> minimal renders correctly for authenticated users when minim
alt=""
aria-expanded={false}
aria-haspopup={true}
aria-label="Account menu for edX"
aria-label="Account menu for edX Test"
className="btn-avatar pgn__avatar-button-avatar pgn__avatar-button-avatar-md dropdown-toggle btn btn-tertiary btn-md"
data-hj-suppress={true}
disabled={false}
Expand All @@ -50,7 +50,6 @@ exports[`<Header /> minimal renders correctly for authenticated users when minim
className="pgn__avatar pgn__avatar-sm"
src="icon/mock/path"
/>
edX
</button>
</div>
</nav>
Expand Down Expand Up @@ -180,7 +179,6 @@ exports[`<Header /> renders correctly for authenticated users on desktop 1`] = `
className="pgn__avatar pgn__avatar-sm"
src="icon/mock/path"
/>
edX
</button>
</div>
</nav>
Expand Down Expand Up @@ -293,7 +291,7 @@ exports[`<Header /> renders correctly for authenticated users on mobile 1`] = `
type="button"
>
<img
alt="edX"
alt="edX Test"
className="pgn__avatar pgn__avatar-sm"
src="icon/mock/path"
/>
Expand Down Expand Up @@ -478,7 +476,7 @@ exports[`<Header /> renders correctly for unauthenticated users on mobile 1`] =
type="button"
>
<img
alt={null}
alt=""
className="pgn__avatar pgn__avatar-sm"
src="icon/mock/path"
/>
Expand Down
34 changes: 34 additions & 0 deletions src/common/UserMenuItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';

import { Avatar } from '@openedx/paragon';
import { injectIntl } from '@edx/frontend-platform/i18n';

import './style.scss';

const UserMenuItem = ({ name, email }) => (
<>
<Avatar
size="sm"
className="mr-2"
alt={name}
data-testid="avatar-icon"
/>
<div className="text-left">
{name && <span className="h5 d-block">{name}</span>}
{email && <span className="small d-block">{email}</span>}
</div>
</>
);

UserMenuItem.propTypes = {
name: PropTypes.string,
email: PropTypes.string,
};

UserMenuItem.defaultProps = {
name: '',
email: '',
};

export default injectIntl(UserMenuItem);
18 changes: 18 additions & 0 deletions src/common/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.dropdown-menu a:first-child {
pointer-events: none;
}

@media screen and (max-width: 768px) {
.menu-content li:first-child {
display: flex;
align-items: center;
padding: 0 16px;
border-bottom: 1px solid #70828E;

a {
display: flex;
align-items: center;
border-bottom: 1px solid #70828E !important;
}
}
}
5 changes: 5 additions & 0 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,8 @@ $white: #fff;
border-radius: $rounded-pill;
}
}

// bottom boarder of first child of user dropdown menu
.dropdown-menu a:first-child {
border-bottom: 1px solid #70828E !important;
}
Loading
Loading