feat: update studio header to be more accessible

This commit is contained in:
KristinAoki
2023-10-02 09:32:16 -04:00
parent 677e872320
commit 9f48ccc66b
18 changed files with 999 additions and 535 deletions

View File

@@ -0,0 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
const BrandNav = ({
studioBaseUrl,
logo,
logoAltText,
}) => (
<a href={studioBaseUrl}>
<img
src={logo}
alt={logoAltText}
className="d-block logo"
/>
</a>
);
BrandNav.propTypes = {
studioBaseUrl: PropTypes.string.isRequired,
logo: PropTypes.string.isRequired,
logoAltText: PropTypes.string.isRequired,
};
export default BrandNav;

View File

@@ -0,0 +1,54 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
OverlayTrigger,
Tooltip,
} from '@edx/paragon';
import messages from './messages';
const CourseLockUp = ({
outlineLink,
org,
number,
title,
// injected
intl,
}) => (
<OverlayTrigger
placement="bottom"
overlay={(
<Tooltip id="course-lock-up">
{title}
</Tooltip>
)}
>
<a
className="course-title-lockup w-25 mr-2"
href={outlineLink}
aria-label={intl.formatMessage(messages['header.label.courseOutline'])}
data-testid="course-lock-up-block"
>
<span className="d-block small m-0 text-gray-800" data-testid="course-org-number">{org} {number}</span>
<span className="d-block m-0 font-weight-bold text-gray-800" data-testid="course-title">{title}</span>
</a>
</OverlayTrigger>
);
CourseLockUp.propTypes = {
number: PropTypes.string,
org: PropTypes.string,
title: PropTypes.string,
outlineLink: PropTypes.string,
// injected
intl: intlShape.isRequired,
};
CourseLockUp.defaultProps = {
number: null,
org: null,
title: null,
outlineLink: null,
};
export default injectIntl(CourseLockUp);

View File

@@ -0,0 +1,155 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
ActionRow,
Button,
Container,
Nav,
Row,
} from '@edx/paragon';
import { Close, MenuIcon } from '@edx/paragon/icons';
import CourseLockUp from './CourseLockUp';
import UserMenu from './UserMenu';
import BrandNav from './BrandNav';
import NavDropdownMenu from './NavDropdownMenu';
const HeaderBody = ({
logo,
logoAltText,
number,
org,
title,
username,
isAdmin,
studioBaseUrl,
logoutUrl,
authenticatedUserAvatar,
isMobile,
setModalPopupTarget,
toggleModalPopup,
isModalPopupOpen,
isHiddenMainMenu,
mainMenuDropdowns,
outlineLink,
}) => {
const renderBrandNav = (
<BrandNav
{...{
studioBaseUrl,
logo,
logoAltText,
}}
/>
);
return (
<Container size="xl" className="px-4">
<ActionRow as="header">
{isHiddenMainMenu ? (
<Row className="flex-nowrap ml-4">
{renderBrandNav}
</Row>
) : (
<>
{isMobile ? (
<Button
ref={setModalPopupTarget}
className="d-inline-flex align-items-center"
variant="tertiary"
onClick={toggleModalPopup}
iconBefore={isModalPopupOpen ? Close : MenuIcon}
data-testid="mobile-menu-button"
>
Menu
</Button>
) : (
<Row className="flex-nowrap m-0">
{renderBrandNav}
<CourseLockUp
{...{
outlineLink,
number,
org,
title,
}}
/>
</Row>
)}
{isMobile ? (
<>
<ActionRow.Spacer />
{renderBrandNav}
</>
) : (
<Nav data-testid="desktop-menu" className="ml-4">
{mainMenuDropdowns.map(dropdown => {
const { id, buttonTitle, items } = dropdown;
return (
<NavDropdownMenu {...{ id, buttonTitle, items }} />
);
})}
</Nav>
)}
</>
)}
<ActionRow.Spacer />
<Nav>
<UserMenu
{...{
username,
studioBaseUrl,
logoutUrl,
authenticatedUserAvatar,
isAdmin,
}}
/>
</Nav>
</ActionRow>
</Container>
);
};
HeaderBody.propTypes = {
studioBaseUrl: PropTypes.string.isRequired,
logoutUrl: PropTypes.string.isRequired,
setModalPopupTarget: PropTypes.func.isRequired,
toggleModalPopup: PropTypes.func.isRequired,
isModalPopupOpen: PropTypes.bool.isRequired,
number: PropTypes.string,
org: PropTypes.string,
title: PropTypes.string,
logo: PropTypes.string,
logoAltText: PropTypes.string,
authenticatedUserAvatar: PropTypes.string,
username: PropTypes.string,
isAdmin: PropTypes.bool,
isMobile: PropTypes.bool,
isHiddenMainMenu: PropTypes.bool,
mainMenuDropdowns: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
buttonTitle: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
href: PropTypes.string,
title: PropTypes.string,
})),
})),
outlineLink: PropTypes.string,
};
HeaderBody.defaultProps = {
logo: null,
logoAltText: null,
number: '',
org: '',
title: '',
authenticatedUserAvatar: null,
username: null,
isAdmin: false,
isMobile: false,
isHiddenMainMenu: false,
mainMenuDropdowns: [],
outlineLink: null,
};
export default HeaderBody;

View File

@@ -0,0 +1,76 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useToggle, ModalPopup } from '@edx/paragon';
import HeaderBody from './HeaderBody';
import MobileMenu from './MobileMenu';
const MobileHeader = ({
mainMenuDropdowns,
...props
}) => {
const [isOpen, , close, toggle] = useToggle(false);
const [target, setTarget] = useState(null);
return (
<>
<HeaderBody
{...props}
isMobile
setModalPopupTarget={setTarget}
toggleModalPopup={toggle}
isModalPopupOpen={isOpen}
/>
<ModalPopup
hasArrow
placement="bottom"
positionRef={target}
isOpen={isOpen}
onClose={close}
onEscapeKey={close}
className="mobile-menu-container"
>
<MobileMenu {...{ mainMenuDropdowns }} />
</ModalPopup>
</>
);
};
MobileHeader.propTypes = {
studioBaseUrl: PropTypes.string.isRequired,
logoutUrl: PropTypes.string.isRequired,
setModalPopupTarget: PropTypes.func.isRequired,
toggleModalPopup: PropTypes.func.isRequired,
isModalPopupOpen: PropTypes.bool.isRequired,
number: PropTypes.string,
org: PropTypes.string,
title: PropTypes.string,
logo: PropTypes.string,
logoAltText: PropTypes.string,
authenticatedUserAvatar: PropTypes.string,
username: PropTypes.string,
isAdmin: PropTypes.bool,
mainMenuDropdowns: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
buttonTitle: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
href: PropTypes.string,
title: PropTypes.string,
})),
})),
outlineLink: PropTypes.string,
};
MobileHeader.defaultProps = {
logo: null,
logoAltText: null,
number: null,
org: null,
title: null,
authenticatedUserAvatar: null,
username: null,
isAdmin: false,
mainMenuDropdowns: [],
outlineLink: null,
};
export default MobileHeader;

View File

@@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Collapsible } from '@edx/paragon';
const MobileMenu = ({
mainMenuDropdowns,
}) => (
<div
className="ml-4 p-2 bg-light-100 border border-gray-200 small rounded"
data-testid="mobile-menu"
>
<div>
{mainMenuDropdowns.map(dropdown => {
const { id, buttonTitle, items } = dropdown;
return (
<Collapsible
className="border-light-100"
title={buttonTitle}
key={id}
>
<ul className="p-0" style={{ listStyleType: 'none' }}>
{items.map(item => (
<li className="mobile-menu-item">
<a href={item.href}>
{item.title}
</a>
</li>
))}
</ul>
</Collapsible>
);
})}
</div>
</div>
);
MobileMenu.propTypes = {
mainMenuDropdowns: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
buttonTitle: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
href: PropTypes.string,
title: PropTypes.string,
})),
})),
};
MobileMenu.defaultProps = {
mainMenuDropdowns: [],
};
export default MobileMenu;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Dropdown,
DropdownButton,
} from '@edx/paragon';
const NavDropdownMenu = ({
id,
buttonTitle,
items,
}) => (
<DropdownButton
id={id}
title={buttonTitle}
variant="tertiary"
>
{items.map(item => (
<Dropdown.Item
href={item.href}
className="small"
>
{item.title}
</Dropdown.Item>
))}
</DropdownButton>
);
NavDropdownMenu.propTypes = {
id: PropTypes.string.isRequired,
buttonTitle: PropTypes.string.isRequired,
items: PropTypes.arrayOf(PropTypes.shape({
href: PropTypes.string,
title: PropTypes.string,
})).isRequired,
};
export default NavDropdownMenu;

View File

@@ -0,0 +1,74 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import Responsive from 'react-responsive';
import { AppContext } from '@edx/frontend-platform/react';
import { ensureConfig } from '@edx/frontend-platform';
import MobileHeader from './MobileHeader';
import HeaderBody from './HeaderBody';
ensureConfig([
'STUDIO_BASE_URL',
'SITE_NAME',
'LOGOUT_URL',
'LOGIN_URL',
'LOGO_URL',
], 'Studio Header component');
const StudioHeader = ({
number, org, title, isHiddenMainMenu, mainMenuDropdowns, outlineLink,
}) => {
const { authenticatedUser, config } = useContext(AppContext);
const props = {
logo: config.LOGO_URL,
logoAltText: `Studio ${config.SITE_NAME}`,
number,
org,
title,
username: authenticatedUser?.username,
isAdmin: authenticatedUser?.administrator,
authenticatedUserAvatar: authenticatedUser?.avatar,
studioBaseUrl: config.STUDIO_BASE_URL,
logoutUrl: config.LOGOUT_URL,
isHiddenMainMenu,
mainMenuDropdowns,
outlineLink,
};
return (
<>
<Responsive maxWidth={768}>
<MobileHeader {...props} />
</Responsive>
<Responsive minWidth={769}>
<HeaderBody {...props} />
</Responsive>
</>
);
};
StudioHeader.propTypes = {
number: PropTypes.string,
org: PropTypes.string,
title: PropTypes.string.isRequired,
isHiddenMainMenu: PropTypes.bool,
mainMenuDropdowns: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
buttonTitle: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
href: PropTypes.string,
title: PropTypes.string,
})),
})),
outlineLink: PropTypes.string,
};
StudioHeader.defaultProps = {
number: '',
org: '',
isHiddenMainMenu: false,
mainMenuDropdowns: [],
outlineLink: null,
};
export default StudioHeader;

View File

@@ -0,0 +1,197 @@
/* eslint-disable react/prop-types */
import React, { useMemo } from 'react';
import {
render,
fireEvent,
waitFor,
} from '@testing-library/react';
import { AppContext } from '@edx/frontend-platform/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { Context as ResponsiveContext } from 'react-responsive';
import StudioHeader from './StudioHeader';
import messages from './messages';
const authenticatedUser = {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
avatar: '/imges/test.png',
};
let currentUser;
let screenWidth = 1280;
const RootWrapper = ({
...props
}) => {
const appContextValue = useMemo(() => ({
authenticatedUser: currentUser,
config: {
LOGOUT_URL: process.env.LOGOUT_URL,
LOGO_URL: process.env.LOGO_URL,
SITE_NAME: process.env.SITE_NAME,
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL,
LOGIN_URL: process.env.LOGIN_URL,
},
}), []);
const responsiveContextValue = useMemo(() => ({ width: screenWidth }), []);
return (
// eslint-disable-next-line react/jsx-no-constructed-context-values, react/prop-types
<IntlProvider locale="en">
<AppContext.Provider value={appContextValue}>
<ResponsiveContext.Provider value={responsiveContextValue}>
<StudioHeader
{...props}
/>
</ResponsiveContext.Provider>
</AppContext.Provider>
</IntlProvider>
);
};
const props = {
number: '123',
org: 'Ed',
title: 'test',
mainMenuDropdowns: [
{
id: 'testId',
buttonTitle: 'test',
items: [
{
title: 'link',
href: '#',
},
],
},
],
outlineLink: 'tEsTLInK',
};
describe('Header', () => {
beforeEach(() => {
jest.clearAllMocks();
currentUser = authenticatedUser;
});
describe('desktop', () => {
it('course lock up should be visible', () => {
const { getByTestId } = render(<RootWrapper {...props} />);
const courseLockUpBlock = getByTestId('course-lock-up-block');
expect(courseLockUpBlock).toBeVisible();
});
it('mobile menu should not be visible', () => {
const { queryByTestId } = render(<RootWrapper {...props} />);
const mobileMenuButton = queryByTestId('mobile-menu-button');
expect(mobileMenuButton).toBeNull();
});
it('desktop menu should be visible', () => {
const { getByTestId } = render(<RootWrapper {...props} />);
const desktopMenu = getByTestId('desktop-menu');
expect(desktopMenu).toBeVisible();
});
it('should render one dropdown', async () => {
const { getAllByRole, getByText } = render(<RootWrapper {...props} />);
const dropdownMenu = getAllByRole('button')[0];
expect(dropdownMenu).toBeVisible();
await waitFor(() => fireEvent.click(dropdownMenu));
const dropdownOption = getByText('link');
expect(dropdownOption).toBeVisible();
});
it('maintenance should not be in user menu', async () => {
currentUser = { ...authenticatedUser, administrator: false };
const { getAllByRole, queryByText } = render(<RootWrapper {...props} />);
const userMenu = getAllByRole('button')[1];
await waitFor(() => fireEvent.click(userMenu));
const maintenanceButton = queryByText(messages['header.user.menu.maintenance'].defaultMessage);
expect(maintenanceButton).toBeNull();
});
it('user menu should use avatar icon', async () => {
currentUser = { ...authenticatedUser, avatar: null };
const { getByTestId } = render(<RootWrapper {...props} />);
const avatarIcon = getByTestId('avatar-icon');
expect(avatarIcon).toBeVisible();
});
it('should hide nav items if prop isHiddenMainMenu true', async () => {
const initialProps = { ...props, isHiddenMainMenu: true };
const { queryByTestId } = render(<RootWrapper {...initialProps} />);
const desktopMenu = queryByTestId('desktop-menu');
const mobileMenuButton = queryByTestId('mobile-menu-button');
expect(mobileMenuButton).toBeNull();
expect(desktopMenu).toBeNull();
});
});
describe('mobile', () => {
beforeEach(() => { screenWidth = 500; });
it('course lock up should not be visible', async () => {
const { queryByTestId } = render(<RootWrapper {...props} />);
const courseLockUpBlock = queryByTestId('course-lock-up-block');
expect(courseLockUpBlock).toBeNull();
});
it('mobile menu should be visible', async () => {
const { getByTestId } = render(<RootWrapper {...props} />);
const mobileMenuButton = getByTestId('mobile-menu-button');
expect(mobileMenuButton).toBeVisible();
await waitFor(() => fireEvent.click(mobileMenuButton));
const mobileMenu = getByTestId('mobile-menu');
expect(mobileMenu).toBeVisible();
});
it('desktop menu should not be visible', () => {
const { queryByTestId } = render(<RootWrapper {...props} />);
const desktopMenu = queryByTestId('desktop-menu');
expect(desktopMenu).toBeNull();
});
it('maintenance should be in user menu', async () => {
const { getAllByRole, getByText } = render(<RootWrapper {...props} />);
const userMenu = getAllByRole('button')[1];
await waitFor(() => fireEvent.click(userMenu));
const maintenanceButton = getByText(messages['header.user.menu.maintenance'].defaultMessage);
expect(maintenanceButton).toBeVisible();
});
it('user menu should use avatar image', async () => {
const { getByTestId } = render(<RootWrapper {...props} />);
const avatarImage = getByTestId('avatar-image');
expect(avatarImage).toBeVisible();
});
it('should hide nav items if prop isHiddenMainMenu true', async () => {
const initialProps = { ...props, isHiddenMainMenu: true };
const { queryByTestId } = render(<RootWrapper {...initialProps} />);
const desktopMenu = queryByTestId('desktop-menu');
const mobileMenuButton = queryByTestId('mobile-menu-button');
expect(mobileMenuButton).toBeNull();
expect(desktopMenu).toBeNull();
});
});
});

View File

@@ -0,0 +1,69 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Avatar,
} from '@edx/paragon';
import NavDropdownMenu from './NavDropdownMenu';
import getUserMenuItems from './utils';
const UserMenu = ({
username,
studioBaseUrl,
logoutUrl,
authenticatedUserAvatar,
isMobile,
isAdmin,
// injected
intl,
}) => {
const avatar = authenticatedUserAvatar ? (
<img
className="d-block w-100 h-100"
src={authenticatedUserAvatar}
alt={username}
data-testid="avatar-image"
/>
) : (
<Avatar
size="sm"
className="mr-2"
alt={username}
data-testid="avatar-icon"
/>
);
const title = isMobile ? avatar : <>{avatar}{username}</>;
return (
<NavDropdownMenu
buttonTitle={title}
id="user-dropdown-menu"
items={getUserMenuItems({
studioBaseUrl,
logoutUrl,
intl,
isAdmin,
})}
/>
);
};
UserMenu.propTypes = {
username: PropTypes.string,
studioBaseUrl: PropTypes.string.isRequired,
logoutUrl: PropTypes.string.isRequired,
authenticatedUserAvatar: PropTypes.string,
isMobile: PropTypes.bool,
isAdmin: PropTypes.bool,
// injected
intl: intlShape.isRequired,
};
UserMenu.defaultProps = {
isMobile: false,
isAdmin: false,
authenticatedUserAvatar: null,
username: null,
};
export default injectIntl(UserMenu);

View File

@@ -0,0 +1,64 @@
// This SCSS was partly copied from edx/frontend-app-support-tools/src/support-header/index.scss.
$spacer: 1rem;
$white: #FFFFFF;
.btn-tertiary:hover {
color: white;
background-color: #00262B;
}
.course-title-lockup {
@media only screen and (max-width: 768px) {
padding-left: .5rem;
max-width: 70%;
}
@media only screen and (min-width: 769px) {
padding: .5rem;
padding-right: $spacer;
border-right: 1px solid #E5E5E5;
min-width: 70%;
}
overflow: hidden;
span {
color: #333333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.375rem;
}
}
.site-header-mobile,
.site-header-desktop {
position: relative;
z-index: 1000;
}
.site-header-mobile {img {
height: 1.5rem;
}
}
.site-header-desktop {
height: 3.75rem;
box-shadow: 0 1px 0 0 rgb(0 0 0 / .1);
background: $white;
.logo {
display: block;
box-sizing: content-box;
position: relative;
top: -.05em;
height: 1.75rem;
padding: $spacer 0;
margin-right: $spacer;
img {
display: block;
height: 100%;
}
}
}

View File

@@ -0,0 +1,3 @@
import StudioHeader from './StudioHeader';
export default StudioHeader;

View File

@@ -0,0 +1,156 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'header.links.content': {
id: 'header.links.content',
defaultMessage: 'Content',
description: 'Label for Content menu trigger',
},
'header.links.settings': {
id: 'header.links.settings',
defaultMessage: 'Settings',
description: 'Label for Settings menu trigger',
},
'header.links.tools': {
id: 'header.links.content.tools',
defaultMessage: 'Tools',
description: 'Label for Tools menu trigger',
},
'header.links.outline': {
id: 'header.links.outline',
defaultMessage: 'Outline',
description: 'Link to Studio Outline page',
},
'header.links.updates': {
id: 'header.links.updates',
defaultMessage: 'Updates',
description: 'Link to Studio Updates page',
},
'header.links.pages': {
id: 'header.links.pages',
defaultMessage: 'Pages & Resources',
description: 'Link to Studio Pages page',
},
'header.links.filesAndUploads': {
id: 'header.links.filesAndUploads',
defaultMessage: 'Files & Uploads',
description: 'Link to Studio Files & Uploads page',
},
'header.links.textbooks': {
id: 'header.links.textbooks',
defaultMessage: 'Textbooks',
description: 'Link to Studio Textbooks page',
},
'header.links.videoUploads': {
id: 'header.links.videoUploads',
defaultMessage: 'Video Uploads',
description: 'Link to Studio Video Uploads page',
},
'header.links.scheduleAndDetails': {
id: 'header.links.scheduleAndDetails',
defaultMessage: 'Schedule & Details',
description: 'Link to Studio Schedule & Details page',
},
'header.links.grading': {
id: 'header.links.grading',
defaultMessage: 'Grading',
description: 'Link to Studio Grading page',
},
'header.links.courseTeam': {
id: 'header.links.courseTeam',
defaultMessage: 'Course Team',
description: 'Link to Studio Course Team page',
},
'header.links.groupConfigurations': {
id: 'header.links.groupConfigurations',
defaultMessage: 'Group Configurations',
description: 'Link to Studio Group Configurations page',
},
'header.links.proctoredExamSettings': {
id: 'header.links.proctoredExamSettings',
defaultMessage: 'Proctored Exam Settings',
description: 'Link to Studio Proctored Exam Settings page',
},
'header.links.advancedSettings': {
id: 'header.links.advancedSettings',
defaultMessage: 'Advanced Settings',
description: 'Link to Studio Advanced Settings page',
},
'header.links.certificates': {
id: 'header.links.certificates',
defaultMessage: 'Certificates',
description: 'Link to Studio Certificates page',
},
'header.links.publisher': {
id: 'header.links.publisher',
defaultMessage: 'Publisher',
description: 'Link to Publisher',
},
'header.links.import': {
id: 'header.links.import',
defaultMessage: 'Import',
description: 'Link to Studio Import page',
},
'header.links.export': {
id: 'header.links.export',
defaultMessage: 'Export',
description: 'Link to Studio Export page',
},
'header.links.checklists': {
id: 'header.links.checklists',
defaultMessage: 'Checklists',
description: 'Link to Studio Checklists page',
},
'header.user.menu.studio': {
id: 'header.user.menu.studio',
defaultMessage: 'Studio Home',
description: 'Link to Studio Home',
},
'header.user.menu.maintenance': {
id: 'header.user.menu.maintenance',
defaultMessage: 'Maintenance',
description: 'Link to the Studio maintenance page',
},
'header.user.menu.logout': {
id: 'header.user.menu.logout',
defaultMessage: 'Logout',
description: 'Logout link',
},
'header.label.account.menu': {
id: 'header.label.account.menu',
defaultMessage: 'Account Menu',
description: 'The aria label for the account menu trigger',
},
'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',
},
'header.label.main.nav': {
id: 'header.label.main.nav',
defaultMessage: 'Main',
description: 'The aria label for the main menu nav',
},
'header.label.main.menu': {
id: 'header.label.main.menu',
defaultMessage: 'Main Menu',
description: 'The aria label for the main menu trigger',
},
'header.label.main.header': {
id: 'header.label.main.header',
defaultMessage: 'Main',
description: 'The aria label for the main header',
},
'header.label.secondary.nav': {
id: 'header.label.secondary.nav',
defaultMessage: 'Secondary',
description: 'The aria label for the seconary nav',
},
'header.label.courseOutline': {
id: 'header.label.courseOutline',
defaultMessage: 'Back to course outline in Studio',
description: 'The aria label for the link back to the Studio Course Outline',
},
});
export default messages;

View File

@@ -0,0 +1,36 @@
import messages from './messages';
const getUserMenuItems = ({
studioBaseUrl,
logoutUrl,
intl,
isAdmin,
}) => {
let items = [
{
href: `${studioBaseUrl}}`,
title: intl.formatMessage(messages['header.user.menu.studio']),
}, {
href: `${logoutUrl}`,
title: intl.formatMessage(messages['header.user.menu.logout']),
},
];
if (isAdmin) {
items = [
{
href: `${studioBaseUrl}}`,
title: intl.formatMessage(messages['header.user.menu.studio']),
}, {
href: `${studioBaseUrl}/maintenance`,
title: intl.formatMessage(messages['header.user.menu.maintenance']),
}, {
href: `${logoutUrl}`,
title: intl.formatMessage(messages['header.user.menu.logout']),
},
];
}
return items;
};
export default getUserMenuItems;