Compare commits

..

9 Commits

Author SHA1 Message Date
Bilal Qamar
6421765100 refactor: updated workflow 2023-02-27 17:55:56 +05:00
Bilal Qamar
f27fc6fa07 refactor: updated workflow 2023-02-27 17:46:40 +05:00
Bilal Qamar
8f052a7ea1 refactor: updated workflow 2023-02-27 17:44:05 +05:00
Bilal Qamar
5e29402f5b refactor: updated workflow 2023-02-27 17:41:16 +05:00
Bilal Qamar
bc0eaf18fc refactor: updated workflow 2023-02-27 17:37:32 +05:00
Bilal Qamar
772008b361 refactor: updated workflow 2023-02-27 17:32:50 +05:00
Bilal Qamar
6e17d53b6a refactor: updated node version to v18 for release dry-run 2023-02-27 17:21:03 +05:00
Bilal Qamar
063ce855ca refactor: fixed build typo 2023-02-27 17:18:00 +05:00
Bilal Qamar
748d4746b3 refactor: added semantic-release-export-data & updated workflows 2023-02-27 17:12:30 +05:00
44 changed files with 34821 additions and 6831 deletions

View File

@@ -5,21 +5,22 @@ on:
- master - master
pull_request: pull_request:
branches: branches:
- '**' - master
jobs: jobs:
tests: tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
node: [16]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Nodejs - name: Setup Nodejs
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VER }} node-version: ${{ matrix.node }}
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Validate package-lock.json changes - name: Validate package-lock.json changes
@@ -34,3 +35,27 @@ jobs:
run: npm run i18n_extract run: npm run i18n_extract
- name: Coverage - name: Coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
get-next-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- run: npx semantic-release --dry-run
id: get-next-version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: echo "The new release version is ${{ needs.get-next-version.outputs.new-release-version }}"
outputs:
new-release-published: ${{ steps.get-next-version.outputs.new-release-published }}
new-release-version: ${{ steps.get-next-version.outputs.new-release-version }}
build:
runs-on: ubuntu-latest
needs: get-next-version
steps:
- uses: actions/checkout@v3
- run: echo "The new release version is ${{ needs.get-next-version.outputs.new-release-version }}"

View File

@@ -10,4 +10,5 @@ on:
jobs: jobs:
version-check: version-check:
uses: openedx/.github/.github/workflows/lockfile-check.yml@master uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master

View File

@@ -9,15 +9,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VER }} node-version: 16
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Validate package-lock.json changes - name: Validate package-lock.json changes
@@ -33,7 +31,7 @@ jobs:
- name: Build - name: Build
run: npm run build run: npm run build
- name: Release - name: Release
uses: cycjimmy/semantic-release-action@v3 uses: cycjimmy/semantic-release-action@v2
with: with:
semantic_version: 16 semantic_version: 16
env: env:

1
.nvmrc
View File

@@ -1 +0,0 @@
18

View File

@@ -1,5 +1,5 @@
{ {
"branch": "master", "branches": ["master", "next"],
"tagFormat": "v${version}", "tagFormat": "v${version}",
"verifyConditions": [ "verifyConditions": [
"@semantic-release/npm", "@semantic-release/npm",
@@ -12,7 +12,13 @@
], ],
"analyzeCommits": "@semantic-release/commit-analyzer", "analyzeCommits": "@semantic-release/commit-analyzer",
"generateNotes": "@semantic-release/release-notes-generator", "generateNotes": "@semantic-release/release-notes-generator",
"prepare": "@semantic-release/npm", "prepare": [
"@semantic-release/npm",
{
"path": "semantic-release-export-data",
"output": "release.json"
}
],
"publish": [ "publish": [
"@semantic-release/npm", "@semantic-release/npm",
{ {

View File

@@ -1,9 +1,11 @@
export TRANSIFEX_RESOURCE = frontend-component-header transifex_resource = frontend-component-header
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA" transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA"
transifex_utils = ./node_modules/.bin/transifex-utils.js transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json transifex_input = $(i18n)/transifex_input.json
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/translation/en/strings/
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/source/
# This directory must match .babelrc . # This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-react-intl transifex_temp = ./temp/babel-plugin-react-intl
@@ -40,11 +42,11 @@ push_translations:
# Pushing strings to Transifex... # Pushing strings to Transifex...
tx push -s tx push -s
# Fetching hashes from Transifex... # Fetching hashes from Transifex...
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh ./node_modules/reactifex/bash_scripts/get_hashed_strings.sh $(tx_url1)
# Writing out comments to file... # Writing out comments to file...
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path $(transifex_utils) $(transifex_temp) --comments
# Pushing comments to Transifex... # Pushing comments to Transifex...
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh ./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
# Pulls translations from Transifex. # Pulls translations from Transifex.
pull_translations: pull_translations:

View File

@@ -2,42 +2,22 @@
frontend-component-header frontend-component-header
######################### #########################
|license| |Build Status| |Codecov| |npm_version| |npm_downloads| |semantic-release| |Build Status| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
******** ********
Purpose Overview
******** ********
A generic header for Open edX micro-frontend applications. A generic header for Open edX micro-frontend applications.
************ ************
Getting Started
************
Prerequisites
=============
The `devstack`_ is currently recommended as a development environment for your
new MFE. If you start it with ``make dev.up.lms`` that should give you
everything you need as a companion to this frontend.
Note that it is also possible to use `Tutor`_ to develop an MFE. You can refer
to the `relevant tutor-mfe documentation`_ to get started using it.
.. _Devstack: https://github.com/openedx/devstack
.. _Tutor: https://github.com/overhangio/tutor
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe#mfe-development
Requirements Requirements
============ ************
This component uses ``@edx/frontend-platform`` services such as i18n, analytics, configuration, and the ``AppContext`` React component, and expects that it has been loaded into a micro-frontend that has been properly initialized via ``@edx/frontend-platform``'s ``initialize`` function. `Please visit the frontend template application to see an example. <https://github.com/openedx/frontend-template-application/blob/master/src/index.jsx>`_ This component uses ``@edx/frontend-platform`` services such as i18n, analytics, configuration, and the ``AppContext`` React component, and expects that it has been loaded into a micro-frontend that has been properly initialized via ``@edx/frontend-platform``'s ``initialize`` function. `Please visit the frontend template application to see an example. <https://github.com/openedx/frontend-template-application/blob/master/src/index.jsx>`_
Environment Variables Environment Variables
==================== =====================
* ``LMS_BASE_URL`` - The URL of the LMS of your Open edX instance. * ``LMS_BASE_URL`` - The URL of the LMS of your Open edX instance.
* ``LOGOUT_URL`` - The URL of the API endpoint which performs a user logout. * ``LOGOUT_URL`` - The URL of the API endpoint which performs a user logout.
@@ -52,8 +32,9 @@ Environment Variables
menu items when truthy. This is intended to be used in micro-frontends like menu items when truthy. This is intended to be used in micro-frontends like
frontend-app-authentication in which these menus are considered distractions from the user's task. frontend-app-authentication in which these menus are considered distractions from the user's task.
************
Installation Installation
============ ************
To install this header into your Open edX micro-frontend, run the following command in your MFE: To install this header into your Open edX micro-frontend, run the following command in your MFE:
@@ -61,33 +42,9 @@ To install this header into your Open edX micro-frontend, run the following comm
This will make the component available to be imported into your application. This will make the component available to be imported into your application.
Cloning and Startup *****
===================
.. code-block::
1. Clone your new repo:
``git clone https://github.com/openedx/frontend-component-header.git``
2. Use node v18.x.
The current version of the micro-frontend build scripts support node 18.
Using other major versions of node *may* work, but this is unsupported. For
convenience, this repository includes an .nvmrc file to help in setting the
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.
3. Install npm dependencies:
``cd frontend-component-header && npm ci``
4. Start the dev server:
``npm start``
Usage Usage
===== *****
This library has the following exports: This library has the following exports:
@@ -101,12 +58,14 @@ Examples
* `An example of component and messages usage. <https://github.com/openedx/frontend-template-application/blob/3355bb3a96232390e9056f35b06ffa8f105ed7ca/src/index.jsx#L21>`_ * `An example of component and messages usage. <https://github.com/openedx/frontend-template-application/blob/3355bb3a96232390e9056f35b06ffa8f105ed7ca/src/index.jsx#L21>`_
* `An example of SCSS file usage. <https://github.com/openedx/frontend-template-application/blob/3cd5485bf387b8c479baf6b02bf59e3061dc3465/src/index.scss#L8>`_ * `An example of SCSS file usage. <https://github.com/openedx/frontend-template-application/blob/3cd5485bf387b8c479baf6b02bf59e3061dc3465/src/index.scss#L8>`_
***********
Development Development
=========== ***********
Install dependencies:: Install dependencies::
npm ci npm i
Start the development server:: Start the development server::
@@ -116,63 +75,6 @@ Build a production distribution::
npm run build npm run build
License
=======
The code in this repository is licensed under the AGPLv3 unless otherwise
noted.
Please see `LICENSE <LICENSE>`_ for details.
Contributing
============
Contributions are very welcome. Please read `How To Contribute`_ for details.
.. _How To Contribute: https://openedx.org/r/how-to-contribute
This project is currently accepting all types of contributions, bug fixes,
security fixes, maintenance work, or new features. However, please make sure
to have a discussion about your new feature idea with the maintainers prior to
beginning development to maximize the chances of your change being accepted.
You can start a conversation by creating a new issue on this repo summarizing
your idea.
Getting Help
===========
If you're having trouble, we have discussion forums at
https://discuss.openedx.org where you can connect with others in the community.
Our real-time conversations are on Slack. You can request a `Slack
invitation`_, then join our `community Slack workspace`_. Because this is a
frontend repository, the best place to discuss it would be in the `#wg-frontend
channel`_.
For anything non-trivial, the best path is to open an issue in this repository
with as many details about the issue you are facing as you can provide.
https://github.com/openedx/frontend-component-header/issues
For more information about these options, see the `Getting Help`_ page.
.. _Slack invitation: https://openedx.org/slack
.. _community Slack workspace: https://openedx.slack.com/
.. _#wg-frontend channel: https://openedx.slack.com/archives/C04BM6YC7A6
.. _Getting Help: https://openedx.org/community/connect
The Open edX Code of Conduct
============================
All community members are expected to follow the `Open edX Code of Conduct`_.
.. _Open edX Code of Conduct: https://openedx.org/code-of-conduct/
Reporting Security Issues
=========================
Please do not report security issues in public. Please email security@openedx.org.
.. |Build Status| image:: https://api.travis-ci.com/edx/frontend-component-header.svg?branch=master .. |Build Status| image:: https://api.travis-ci.com/edx/frontend-component-header.svg?branch=master
:target: https://travis-ci.com/edx/frontend-component-header :target: https://travis-ci.com/edx/frontend-component-header
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-component-header .. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-component-header
@@ -184,4 +86,4 @@ Please do not report security issues in public. Please email security@openedx.or
.. |license| image:: https://img.shields.io/npm/l/@edx/frontend-component-header.svg .. |license| image:: https://img.shields.io/npm/l/@edx/frontend-component-header.svg
:target: @edx/frontend-component-header :target: @edx/frontend-component-header
.. |semantic-release| image:: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg .. |semantic-release| image:: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
:target: https://github.com/semantic-release/semantic-release :target: https://github.com/semantic-release/semantic-release

39566
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,46 +31,49 @@
"bugs": { "bugs": {
"url": "https://github.com/openedx/frontend-component-header/issues" "url": "https://github.com/openedx/frontend-component-header/issues"
}, },
"release": {
"branches": ["master", "next"]
},
"homepage": "https://github.com/openedx/frontend-component-header#readme", "homepage": "https://github.com/openedx/frontend-component-header#readme",
"devDependencies": { "devDependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.2.0", "@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@edx/browserslist-config": "^1.1.1", "@edx/browserslist-config": "^1.1.1",
"@edx/frontend-build": "12.9.17", "@edx/frontend-build": "^12.4.19",
"@edx/frontend-platform": "6.0.0", "@edx/frontend-platform": "^3.0.1",
"@edx/reactifex": "^2.1.1", "@testing-library/dom": "8.20.0",
"@testing-library/dom": "9.3.3", "@testing-library/jest-dom": "5.16.5",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "10.4.9", "@testing-library/react": "10.4.9",
"@wojtekmaj/enzyme-adapter-react-17": "0.8.0",
"enzyme": "3.11.0", "enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.7",
"husky": "8.0.3", "husky": "8.0.3",
"jest": "29.7.0", "jest": "29.4.3",
"jest-chain": "1.1.6", "jest-chain": "1.1.6",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"react": "17.0.2", "react": "16.14.0",
"react-dom": "17.0.2", "react-dom": "16.14.0",
"react-redux": "7.2.9", "react-redux": "7.2.9",
"react-router-dom": "6.16.0", "react-router-dom": "5.3.4",
"react-test-renderer": "17.0.2", "react-test-renderer": "16.14.0",
"reactifex": "1.1.1",
"redux": "4.2.1", "redux": "4.2.1",
"redux-saga": "1.2.3" "redux-saga": "1.2.2",
"semantic-release-export-data": "^1.0.1"
}, },
"dependencies": { "dependencies": {
"@edx/paragon": "21.5.3", "@edx/paragon": "20.28.4",
"@fortawesome/fontawesome-svg-core": "6.4.2", "@fortawesome/fontawesome-svg-core": "6.3.0",
"@fortawesome/free-brands-svg-icons": "6.4.2", "@fortawesome/free-brands-svg-icons": "6.3.0",
"@fortawesome/free-regular-svg-icons": "6.4.2", "@fortawesome/free-regular-svg-icons": "6.3.0",
"@fortawesome/free-solid-svg-icons": "6.4.2", "@fortawesome/free-solid-svg-icons": "6.3.0",
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"axios-mock-adapter": "1.21.5",
"babel-polyfill": "6.26.0", "babel-polyfill": "6.26.0",
"react-responsive": "8.2.0", "react-responsive": "8.2.0",
"react-transition-group": "4.4.5" "react-transition-group": "4.4.5"
}, },
"peerDependencies": { "peerDependencies": {
"@edx/frontend-platform": "^4.0.0 || ^5.0.0 || ^6.0.0", "@edx/frontend-platform": "^2.0.0 || ^3.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.9.0 || ^17.0.0", "react": "^16.9.0",
"react-dom": "^16.9.0 || ^17.0.0" "react-dom": "^16.9.0"
} }
} }

View File

@@ -24,7 +24,7 @@
"automerge": true "automerge": true
}, },
{ {
"matchPackagePatterns": ["@edx", "@openedx"], "matchPackagePatterns": ["@edx"],
"matchUpdateTypes": ["minor", "patch"], "matchUpdateTypes": ["minor", "patch"],
"automerge": true "automerge": true
} }

200
src/StudioHeader.jsx Normal file
View File

@@ -0,0 +1,200 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import {
APP_CONFIG_INITIALIZED,
ensureConfig,
getConfig,
mergeConfig,
subscribe,
} from '@edx/frontend-platform';
import { ActionRow } from '@edx/paragon';
import { Menu, MenuTrigger, MenuContent } from './Menu';
import Avatar from './Avatar';
import { LinkedLogo, Logo } from './Logo';
import { CaretIcon } from './Icons';
import messages from './Header.messages';
ensureConfig([
'STUDIO_BASE_URL',
'LOGOUT_URL',
'LOGIN_URL',
'SITE_NAME',
'LOGO_URL',
'ORDER_HISTORY_URL',
], 'StudioHeader component');
subscribe(APP_CONFIG_INITIALIZED, () => {
mergeConfig({
AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER,
}, 'StudioHeader additional config');
});
class StudioDesktopHeaderBase extends React.Component {
constructor(props) { // eslint-disable-line no-useless-constructor
super(props);
}
renderUserMenu() {
const {
userMenu,
avatar,
username,
intl,
} = this.props;
return (
<Menu transitionClassName="menu-dropdown" transitionTimeout={250}>
<MenuTrigger
tag="button"
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { username })}
className="btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
>
<Avatar size="1.5em" src={avatar} alt="" className="mr-2" />
{username} <CaretIcon role="img" aria-hidden focusable="false" />
</MenuTrigger>
<MenuContent className="mb-0 dropdown-menu show dropdown-menu-right pin-right shadow py-2">
{userMenu.map(({ type, href, content }) => (
<a className={`dropdown-${type}`} key={`${type}-${content}`} href={href}>{content}</a>
))}
</MenuContent>
</Menu>
);
}
renderLoggedOutItems() {
const { loggedOutItems } = this.props;
return loggedOutItems.map((item, i, arr) => (
<a
key={`${item.type}-${item.content}`}
className={i < arr.length - 1 ? 'btn mr-2 btn-link' : 'btn mr-2 btn-outline-primary'}
href={item.href}
>
{item.content}
</a>
));
}
render() {
const {
logo,
logoAltText,
logoDestination,
loggedIn,
intl,
actionRowContent,
} = this.props;
const logoProps = { src: logo, alt: logoAltText, href: logoDestination };
const logoClasses = getConfig().AUTHN_MINIMAL_HEADER ? 'mw-100' : null;
return (
<header className="site-header-desktop">
<a className="nav-skip sr-only sr-only-focusable" href="#main">{intl.formatMessage(messages['header.label.skip.nav'])}</a>
<div className={`container-fluid ${logoClasses}`}>
<div className="nav-container position-relative d-flex align-items-center">
{logoDestination === null ? <Logo className="logo" src={logo} alt={logoAltText} /> : <LinkedLogo className="logo" {...logoProps} />}
<ActionRow>
{actionRowContent}
<nav
aria-label={intl.formatMessage(messages['header.label.secondary.nav'])}
className="nav secondary-menu-container align-items-center ml-auto"
>
{loggedIn ? this.renderUserMenu() : this.renderLoggedOutItems()}
</nav>
</ActionRow>
</div>
</div>
</header>
);
}
}
StudioDesktopHeaderBase.propTypes = {
userMenu: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
})),
loggedOutItems: PropTypes.arrayOf(PropTypes.shape({
type: PropTypes.oneOf(['item', 'menu']),
href: PropTypes.string,
content: PropTypes.string,
})),
logo: PropTypes.string,
logoAltText: PropTypes.string,
logoDestination: PropTypes.string,
avatar: PropTypes.string,
username: PropTypes.string,
loggedIn: PropTypes.bool,
actionRowContent: PropTypes.element,
// i18n
intl: intlShape.isRequired,
};
StudioDesktopHeaderBase.defaultProps = {
userMenu: [],
loggedOutItems: [],
logo: null,
logoAltText: null,
logoDestination: null,
avatar: null,
username: null,
loggedIn: false,
actionRowContent: null,
};
const StudioDesktopHeader = injectIntl(StudioDesktopHeaderBase);
const StudioHeader = ({ intl, actionRowContent }) => {
const { authenticatedUser, config } = useContext(AppContext);
const userMenu = authenticatedUser === null ? [] : [
{
type: 'item',
href: `${config.STUDIO_BASE_URL}`,
content: intl.formatMessage(messages['header.user.menu.studio.home']),
},
{
type: 'item',
href: `${config.STUDIO_BASE_URL}/maintenance`,
content: intl.formatMessage(messages['header.user.menu.studio.maintenance']),
},
{
type: 'item',
href: config.LOGOUT_URL,
content: intl.formatMessage(messages['header.user.menu.logout']),
},
];
const props = {
logo: config.LOGO_URL,
logoAltText: config.SITE_NAME,
logoDestination: config.STUDIO_BASE_URL,
loggedIn: authenticatedUser !== null,
username: authenticatedUser !== null ? authenticatedUser.username : null,
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
actionRowContent,
userMenu,
loggedOutItems: [],
};
return <StudioDesktopHeader {...props} />;
};
StudioHeader.propTypes = {
intl: intlShape.isRequired,
actionRowContent: PropTypes.element,
};
StudioHeader.defaultProps = {
// eslint-disable-next-line react/jsx-no-useless-fragment
actionRowContent: <></>,
};
export default injectIntl(StudioHeader);

108
src/StudioHeader.test.jsx Normal file
View File

@@ -0,0 +1,108 @@
/* eslint-disable react/prop-types */
import React, { useMemo } from 'react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import TestRenderer from 'react-test-renderer';
import { Link } from 'react-router-dom';
import { AppContext } from '@edx/frontend-platform/react';
import {
ActionRow,
Button,
Dropdown,
} from '@edx/paragon';
import { StudioHeader } from './index';
const StudioHeaderComponent = ({ contextValue, appMenu = null, mainMenu = [] }) => (
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider
value={contextValue}
>
<StudioHeader appMenu={appMenu} mainMenu={mainMenu} />
</AppContext.Provider>
</IntlProvider>
);
const StudioHeaderContext = ({ actionRowContent = null }) => {
const headerContextValue = useMemo(() => ({
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
LOGO_URL: process.env.LOGO_URL,
},
}), []);
return (
<IntlProvider locale="en" messages={{}}>
<AppContext.Provider
value={headerContextValue}
>
<StudioHeader actionRowContent={actionRowContent} />
</AppContext.Provider>
</IntlProvider>
);
};
describe('<StudioHeader />', () => {
it('renders correctly', () => {
const contextValue = {
authenticatedUser: {
userId: 'abc123',
username: 'edX',
roles: [],
administrator: false,
},
config: {
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
LOGO_URL: process.env.LOGO_URL,
},
};
const component = <StudioHeaderComponent contextValue={contextValue} />;
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('renders correctly with optional action row content', () => {
const actionRowContent = (
<>
<Dropdown>
<Dropdown.Toggle variant="outline-primary" id="library-header-menu-dropdown">
Settings
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item as={Link} to="#">Dropdown Item 1</Dropdown.Item>
<Dropdown.Item as={Link} to="#">Dropdown Item 2</Dropdown.Item>
<Dropdown.Item as={Link} to="#">Dropdown Item 3</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<ActionRow.Spacer />
<Button
variant="tertiary"
href="#"
rel="noopener noreferrer"
target="_blank"
title="Help Button"
>Help
</Button>
</>
);
const component = <StudioHeaderContext actionRowContent={actionRowContent} />;
const wrapper = TestRenderer.create(component);
expect(wrapper.toJSON()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,226 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<StudioHeader /> renders correctly 1`] = `
<header
className="site-header-desktop"
>
<a
className="nav-skip sr-only sr-only-focusable"
href="#main"
>
Skip to main content
</a>
<div
className="container-fluid null"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<img
alt="edX"
className="logo"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
<div
className="pgn__action-row"
>
<nav
aria-label="Secondary"
className="nav secondary-menu-container align-items-center ml-auto"
>
<div
className="menu null"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account menu for edX"
className="menu-trigger btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
onClick={[Function]}
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle mr-2"
style={
Object {
"height": "1.5em",
"width": "1.5em",
}
}
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5em",
"width": "1.5em",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
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"
fill="currentColor"
/>
</svg>
</span>
edX
<svg
aria-hidden={true}
focusable="false"
height="16px"
role="img"
version="1.1"
viewBox="0 0 16 16"
width="16px"
>
<path
d="M7,4 L7,8 L11,8 L11,10 L5,10 L5,4 L7,4 Z"
fill="currentColor"
transform="translate(8.000000, 7.000000) rotate(-45.000000) translate(-8.000000, -7.000000) "
/>
</svg>
</button>
</div>
</nav>
</div>
</div>
</div>
</header>
`;
exports[`<StudioHeader /> renders correctly with optional action row content 1`] = `
<header
className="site-header-desktop"
>
<a
className="nav-skip sr-only sr-only-focusable"
href="#main"
>
Skip to main content
</a>
<div
className="container-fluid null"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<img
alt="edX"
className="logo"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
<div
className="pgn__action-row"
>
<div
className="pgn__dropdown pgn__dropdown-light dropdown"
data-testid="dropdown"
>
<button
aria-expanded={false}
aria-haspopup={true}
className="dropdown-toggle btn btn-outline-primary"
disabled={false}
id="library-header-menu-dropdown"
onClick={[Function]}
type="button"
>
Settings
</button>
</div>
<span
className="pgn__action-row-spacer"
/>
<a
className="btn btn-tertiary"
href="#"
onClick={[Function]}
onKeyDown={[Function]}
rel="noopener noreferrer"
role="button"
target="_blank"
title="Help Button"
>
Help
</a>
<nav
aria-label="Secondary"
className="nav secondary-menu-container align-items-center ml-auto"
>
<div
className="menu null"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account menu for edX"
className="menu-trigger btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
onClick={[Function]}
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle mr-2"
style={
Object {
"height": "1.5em",
"width": "1.5em",
}
}
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5em",
"width": "1.5em",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
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"
fill="currentColor"
/>
</svg>
</span>
edX
<svg
aria-hidden={true}
focusable="false"
height="16px"
role="img"
version="1.1"
viewBox="0 0 16 16"
width="16px"
>
<path
d="M7,4 L7,8 L11,8 L11,10 L5,10 L5,4 L7,4 Z"
fill="currentColor"
transform="translate(8.000000, 7.000000) rotate(-45.000000) translate(-8.000000, -7.000000) "
/>
</svg>
</button>
</div>
</nav>
</div>
</div>
</div>
</header>
`;

View File

@@ -29,28 +29,5 @@
"header.menu.account.label": "الحساب", "header.menu.account.label": "الحساب",
"header.menu.orderHistory.label": "سجل الطلبيات", "header.menu.orderHistory.label": "سجل الطلبيات",
"header.navigation.skipNavLink": "التخطي إلى المحتوى الرئيسي", "header.navigation.skipNavLink": "التخطي إلى المحتوى الرئيسي",
"header.menu.signOut.label": "تسجيل الخروج", "header.menu.signOut.label": "تسجيل الخروج"
"header.links.content": "Content",
"header.links.settings": "Settings",
"header.links.content.tools": "Tools",
"header.links.outline": "Outline",
"header.links.updates": "Updates",
"header.links.pages": "Pages & Resources",
"header.links.filesAndUploads": "Files & Uploads",
"header.links.textbooks": "Textbooks",
"header.links.videoUploads": "Video Uploads",
"header.links.scheduleAndDetails": "Schedule & Details",
"header.links.grading": "Grading",
"header.links.courseTeam": "Course Team",
"header.links.groupConfigurations": "Group Configurations",
"header.links.proctoredExamSettings": "Proctored Exam Settings",
"header.links.advancedSettings": "Advanced Settings",
"header.links.certificates": "Certificates",
"header.links.publisher": "Publisher",
"header.links.import": "Import",
"header.links.export": "Export",
"header.links.checklists": "Checklists",
"header.user.menu.studio": "Studio Home",
"header.user.menu.maintenance": "Maintenance",
"header.label.courseOutline": "Back to course outline in Studio"
} }

View File

@@ -29,28 +29,5 @@
"header.menu.account.label": "Account", "header.menu.account.label": "Account",
"header.menu.orderHistory.label": "Order History", "header.menu.orderHistory.label": "Order History",
"header.navigation.skipNavLink": "Skip to main content.", "header.navigation.skipNavLink": "Skip to main content.",
"header.menu.signOut.label": "Sign Out", "header.menu.signOut.label": "Sign Out"
"header.links.content": "Content",
"header.links.settings": "Settings",
"header.links.content.tools": "Tools",
"header.links.outline": "Outline",
"header.links.updates": "Updates",
"header.links.pages": "Pages & Resources",
"header.links.filesAndUploads": "Files & Uploads",
"header.links.textbooks": "Textbooks",
"header.links.videoUploads": "Video Uploads",
"header.links.scheduleAndDetails": "Schedule & Details",
"header.links.grading": "Grading",
"header.links.courseTeam": "Course Team",
"header.links.groupConfigurations": "Group Configurations",
"header.links.proctoredExamSettings": "Proctored Exam Settings",
"header.links.advancedSettings": "Advanced Settings",
"header.links.certificates": "Certificates",
"header.links.publisher": "Publisher",
"header.links.import": "Import",
"header.links.export": "Export",
"header.links.checklists": "Checklists",
"header.user.menu.studio": "Studio Home",
"header.user.menu.maintenance": "Maintenance",
"header.label.courseOutline": "Back to course outline in Studio"
} }

View File

@@ -29,28 +29,5 @@
"header.menu.account.label": "Cuenta", "header.menu.account.label": "Cuenta",
"header.menu.orderHistory.label": "Historial de órdenes", "header.menu.orderHistory.label": "Historial de órdenes",
"header.navigation.skipNavLink": "Dirígete al contenido principal.", "header.navigation.skipNavLink": "Dirígete al contenido principal.",
"header.menu.signOut.label": "Cerrar sesión", "header.menu.signOut.label": "Cerrar sesión"
"header.links.content": "Contenido",
"header.links.settings": "Configuración",
"header.links.content.tools": "Herramientas",
"header.links.outline": "Estructura",
"header.links.updates": "Actualizaciones",
"header.links.pages": "Páginas & Recursos",
"header.links.filesAndUploads": "Administración de archivos",
"header.links.textbooks": "Libros de texto",
"header.links.videoUploads": "Carga de videos",
"header.links.scheduleAndDetails": "Calendario y detalles",
"header.links.grading": "Calificaciones",
"header.links.courseTeam": "Equipo del curso",
"header.links.groupConfigurations": "Configuraciones de Grupo",
"header.links.proctoredExamSettings": "Configuración de Exámenes Supervisados",
"header.links.advancedSettings": "Configuración avanzada",
"header.links.certificates": "Certificados",
"header.links.publisher": "Publisher",
"header.links.import": "Importar",
"header.links.export": "Exportar",
"header.links.checklists": "Listas de chequeo",
"header.user.menu.studio": "Inicio Studio",
"header.user.menu.maintenance": "Mantenimiento",
"header.label.courseOutline": "Volver al esquema del curso en Studio"
} }

View File

@@ -29,28 +29,5 @@
"header.menu.account.label": "Compte", "header.menu.account.label": "Compte",
"header.menu.orderHistory.label": "Historique des commandes", "header.menu.orderHistory.label": "Historique des commandes",
"header.navigation.skipNavLink": "Passer au contenu principal", "header.navigation.skipNavLink": "Passer au contenu principal",
"header.menu.signOut.label": "Se déconnecter", "header.menu.signOut.label": "Se déconnecter"
"header.links.content": "Content",
"header.links.settings": "Settings",
"header.links.content.tools": "Tools",
"header.links.outline": "Outline",
"header.links.updates": "Updates",
"header.links.pages": "Pages & Resources",
"header.links.filesAndUploads": "Files & Uploads",
"header.links.textbooks": "Textbooks",
"header.links.videoUploads": "Video Uploads",
"header.links.scheduleAndDetails": "Schedule & Details",
"header.links.grading": "Grading",
"header.links.courseTeam": "Course Team",
"header.links.groupConfigurations": "Group Configurations",
"header.links.proctoredExamSettings": "Proctored Exam Settings",
"header.links.advancedSettings": "Advanced Settings",
"header.links.certificates": "Certificates",
"header.links.publisher": "Publisher",
"header.links.import": "Import",
"header.links.export": "Export",
"header.links.checklists": "Checklists",
"header.user.menu.studio": "Studio Home",
"header.user.menu.maintenance": "Maintenance",
"header.label.courseOutline": "Back to course outline in Studio"
} }

View File

@@ -29,28 +29,5 @@
"header.menu.account.label": "Compte", "header.menu.account.label": "Compte",
"header.menu.orderHistory.label": "Historique des commandes", "header.menu.orderHistory.label": "Historique des commandes",
"header.navigation.skipNavLink": "Passer au contenu principal.", "header.navigation.skipNavLink": "Passer au contenu principal.",
"header.menu.signOut.label": "Se déconnecter", "header.menu.signOut.label": "Se déconnecter"
"header.links.content": "Contenu",
"header.links.settings": "Paramètres",
"header.links.content.tools": "Outils",
"header.links.outline": "Plan de cours",
"header.links.updates": "Annonces",
"header.links.pages": "Pages et ressources",
"header.links.filesAndUploads": "Fichiers et téléversements",
"header.links.textbooks": "Manuels",
"header.links.videoUploads": "Téléversements des vidéos",
"header.links.scheduleAndDetails": "Dates et détails",
"header.links.grading": "Évaluation",
"header.links.courseTeam": "Équipe de cours",
"header.links.groupConfigurations": "Configuration des groupes",
"header.links.proctoredExamSettings": "Paramètres d'examen surveillé",
"header.links.advancedSettings": "Paramètres avancés",
"header.links.certificates": "Attestations",
"header.links.publisher": "Éditeur",
"header.links.import": "Importer",
"header.links.export": "Exporter",
"header.links.checklists": "Listes de contrôle",
"header.user.menu.studio": "Accueil Studio",
"header.user.menu.maintenance": "Entretien",
"header.label.courseOutline": "Retour au plan de cours dans Studio"
} }

View File

@@ -29,28 +29,5 @@
"header.menu.account.label": "Account", "header.menu.account.label": "Account",
"header.menu.orderHistory.label": "Order History", "header.menu.orderHistory.label": "Order History",
"header.navigation.skipNavLink": "Skip to main content.", "header.navigation.skipNavLink": "Skip to main content.",
"header.menu.signOut.label": "Sign Out", "header.menu.signOut.label": "Sign Out"
"header.links.content": "Content",
"header.links.settings": "Settings",
"header.links.content.tools": "Tools",
"header.links.outline": "Outline",
"header.links.updates": "Updates",
"header.links.pages": "Pages & Resources",
"header.links.filesAndUploads": "Files & Uploads",
"header.links.textbooks": "Textbooks",
"header.links.videoUploads": "Video Uploads",
"header.links.scheduleAndDetails": "Schedule & Details",
"header.links.grading": "Grading",
"header.links.courseTeam": "Course Team",
"header.links.groupConfigurations": "Group Configurations",
"header.links.proctoredExamSettings": "Proctored Exam Settings",
"header.links.advancedSettings": "Advanced Settings",
"header.links.certificates": "Certificates",
"header.links.publisher": "Publisher",
"header.links.import": "Import",
"header.links.export": "Export",
"header.links.checklists": "Checklists",
"header.user.menu.studio": "Studio Home",
"header.user.menu.maintenance": "Maintenance",
"header.label.courseOutline": "Back to course outline in Studio"
} }

View File

@@ -29,28 +29,5 @@
"header.menu.account.label": "Account", "header.menu.account.label": "Account",
"header.menu.orderHistory.label": "Order History", "header.menu.orderHistory.label": "Order History",
"header.navigation.skipNavLink": "Skip to main content.", "header.navigation.skipNavLink": "Skip to main content.",
"header.menu.signOut.label": "Sign Out", "header.menu.signOut.label": "Sign Out"
"header.links.content": "Content",
"header.links.settings": "Settings",
"header.links.content.tools": "Tools",
"header.links.outline": "Outline",
"header.links.updates": "Updates",
"header.links.pages": "Pages & Resources",
"header.links.filesAndUploads": "Files & Uploads",
"header.links.textbooks": "Textbooks",
"header.links.videoUploads": "Video Uploads",
"header.links.scheduleAndDetails": "Schedule & Details",
"header.links.grading": "Grading",
"header.links.courseTeam": "Course Team",
"header.links.groupConfigurations": "Group Configurations",
"header.links.proctoredExamSettings": "Proctored Exam Settings",
"header.links.advancedSettings": "Advanced Settings",
"header.links.certificates": "Certificates",
"header.links.publisher": "Publisher",
"header.links.import": "Import",
"header.links.export": "Export",
"header.links.checklists": "Checklists",
"header.user.menu.studio": "Studio Home",
"header.user.menu.maintenance": "Maintenance",
"header.label.courseOutline": "Back to course outline in Studio"
} }

View File

@@ -29,28 +29,5 @@
"header.menu.account.label": "Account", "header.menu.account.label": "Account",
"header.menu.orderHistory.label": "Order History", "header.menu.orderHistory.label": "Order History",
"header.navigation.skipNavLink": "Skip to main content.", "header.navigation.skipNavLink": "Skip to main content.",
"header.menu.signOut.label": "Sign Out", "header.menu.signOut.label": "Sign Out"
"header.links.content": "Content",
"header.links.settings": "Settings",
"header.links.content.tools": "Tools",
"header.links.outline": "Outline",
"header.links.updates": "Updates",
"header.links.pages": "Pages & Resources",
"header.links.filesAndUploads": "Files & Uploads",
"header.links.textbooks": "Textbooks",
"header.links.videoUploads": "Video Uploads",
"header.links.scheduleAndDetails": "Schedule & Details",
"header.links.grading": "Grading",
"header.links.courseTeam": "Course Team",
"header.links.groupConfigurations": "Group Configurations",
"header.links.proctoredExamSettings": "Proctored Exam Settings",
"header.links.advancedSettings": "Advanced Settings",
"header.links.certificates": "Certificates",
"header.links.publisher": "Publisher",
"header.links.import": "Import",
"header.links.export": "Export",
"header.links.checklists": "Checklists",
"header.user.menu.studio": "Studio Home",
"header.user.menu.maintenance": "Maintenance",
"header.label.courseOutline": "Back to course outline in Studio"
} }

View File

@@ -29,28 +29,5 @@
"header.menu.account.label": "Account", "header.menu.account.label": "Account",
"header.menu.orderHistory.label": "Order History", "header.menu.orderHistory.label": "Order History",
"header.navigation.skipNavLink": "Skip to main content.", "header.navigation.skipNavLink": "Skip to main content.",
"header.menu.signOut.label": "Sign Out", "header.menu.signOut.label": "Sign Out"
"header.links.content": "Content",
"header.links.settings": "Settings",
"header.links.content.tools": "Tools",
"header.links.outline": "Outline",
"header.links.updates": "Updates",
"header.links.pages": "Pages & Resources",
"header.links.filesAndUploads": "Files & Uploads",
"header.links.textbooks": "Textbooks",
"header.links.videoUploads": "Video Uploads",
"header.links.scheduleAndDetails": "Schedule & Details",
"header.links.grading": "Grading",
"header.links.courseTeam": "Course Team",
"header.links.groupConfigurations": "Group Configurations",
"header.links.proctoredExamSettings": "Proctored Exam Settings",
"header.links.advancedSettings": "Advanced Settings",
"header.links.certificates": "Certificates",
"header.links.publisher": "Publisher",
"header.links.import": "Import",
"header.links.export": "Export",
"header.links.checklists": "Checklists",
"header.user.menu.studio": "Studio Home",
"header.user.menu.maintenance": "Maintenance",
"header.label.courseOutline": "Back to course outline in Studio"
} }

View File

@@ -1,8 +1,8 @@
{ {
"general.register.sentenceCase": "Register", "general.register.sentenceCase": "Register",
"general.signIn.sentenceCase": "Увійти", "general.signIn.sentenceCase": "Sign in",
"header.links.courses": "Курси", "header.links.courses": "Courses",
"header.links.programs": "Програми", "header.links.programs": "Programs",
"header.links.content.search": "Discover New", "header.links.content.search": "Discover New",
"header.links.schools": "Schools & Partners", "header.links.schools": "Schools & Partners",
"header.user.menu.dashboard": "Dashboard", "header.user.menu.dashboard": "Dashboard",
@@ -15,42 +15,19 @@
"header.user.menu.studio.home": "Studio Home", "header.user.menu.studio.home": "Studio Home",
"header.user.menu.studio.maintenance": "Maintenance", "header.user.menu.studio.maintenance": "Maintenance",
"header.label.account.nav": "Account", "header.label.account.nav": "Account",
"header.label.account.menu": "Меню облікового запису", "header.label.account.menu": "Account Menu",
"header.label.account.menu.for": "Меню облікового запису для {username}", "header.label.account.menu.for": "Account menu for {username}",
"header.label.main.nav": "Main", "header.label.main.nav": "Main",
"header.label.main.menu": "Main Menu", "header.label.main.menu": "Main Menu",
"header.label.main.header": "Main", "header.label.main.header": "Main",
"header.label.secondary.nav": "Secondary", "header.label.secondary.nav": "Secondary",
"header.label.skip.nav": "Перейти до головного змісту", "header.label.skip.nav": "Skip to main content",
"header.label.app.nav": "App", "header.label.app.nav": "App",
"header.menu.dashboard.label": "Dashboard", "header.menu.dashboard.label": "Dashboard",
"header.help.label": "Help", "header.help.label": "Help",
"header.menu.profile.label": "Profile", "header.menu.profile.label": "Profile",
"header.menu.account.label": "Account", "header.menu.account.label": "Account",
"header.menu.orderHistory.label": "Order History", "header.menu.orderHistory.label": "Order History",
"header.navigation.skipNavLink": "Перейти до головного змісту.", "header.navigation.skipNavLink": "Skip to main content.",
"header.menu.signOut.label": "Sign Out", "header.menu.signOut.label": "Sign Out"
"header.links.content": "Content",
"header.links.settings": "Settings",
"header.links.content.tools": "Tools",
"header.links.outline": "Outline",
"header.links.updates": "Updates",
"header.links.pages": "Pages & Resources",
"header.links.filesAndUploads": "Files & Uploads",
"header.links.textbooks": "Textbooks",
"header.links.videoUploads": "Video Uploads",
"header.links.scheduleAndDetails": "Schedule & Details",
"header.links.grading": "Grading",
"header.links.courseTeam": "Course Team",
"header.links.groupConfigurations": "Group Configurations",
"header.links.proctoredExamSettings": "Proctored Exam Settings",
"header.links.advancedSettings": "Advanced Settings",
"header.links.certificates": "Certificates",
"header.links.publisher": "Publisher",
"header.links.import": "Import",
"header.links.export": "Export",
"header.links.checklists": "Checklists",
"header.user.menu.studio": "Studio Home",
"header.user.menu.maintenance": "Maintenance",
"header.label.courseOutline": "Back to course outline in Studio"
} }

View File

@@ -29,28 +29,5 @@
"header.menu.account.label": "Account", "header.menu.account.label": "Account",
"header.menu.orderHistory.label": "Order History", "header.menu.orderHistory.label": "Order History",
"header.navigation.skipNavLink": "Skip to main content.", "header.navigation.skipNavLink": "Skip to main content.",
"header.menu.signOut.label": "Sign Out", "header.menu.signOut.label": "Sign Out"
"header.links.content": "Content",
"header.links.settings": "Settings",
"header.links.content.tools": "Tools",
"header.links.outline": "Outline",
"header.links.updates": "Updates",
"header.links.pages": "Pages & Resources",
"header.links.filesAndUploads": "Files & Uploads",
"header.links.textbooks": "Textbooks",
"header.links.videoUploads": "Video Uploads",
"header.links.scheduleAndDetails": "Schedule & Details",
"header.links.grading": "Grading",
"header.links.courseTeam": "Course Team",
"header.links.groupConfigurations": "Group Configurations",
"header.links.proctoredExamSettings": "Proctored Exam Settings",
"header.links.advancedSettings": "Advanced Settings",
"header.links.certificates": "Certificates",
"header.links.publisher": "Publisher",
"header.links.import": "Import",
"header.links.export": "Export",
"header.links.checklists": "Checklists",
"header.user.menu.studio": "Studio Home",
"header.user.menu.maintenance": "Maintenance",
"header.label.courseOutline": "Back to course outline in Studio"
} }

View File

@@ -1,7 +1,7 @@
import Header from './Header'; import Header from './Header';
import LearningHeader from './learning-header/LearningHeader'; import LearningHeader from './learning-header/LearningHeader';
import messages from './i18n/index'; import messages from './i18n/index';
import StudioHeader from './studio-header'; import StudioHeader from './StudioHeader';
export { LearningHeader, messages, StudioHeader }; export { LearningHeader, messages, StudioHeader };

View File

@@ -3,7 +3,6 @@ $blue: #007db8;
$white: #fff; $white: #fff;
@import './Menu/menu.scss'; @import './Menu/menu.scss';
@import './studio-header/header.scss';
.dropdown-item a { .dropdown-item a {
text-decoration: none; text-decoration: none;
@@ -28,7 +27,7 @@ $white: #fff;
.learning-header { .learning-header {
min-width: 0; min-width: 0;
.course-title-lockup { .course-title-lockup {
min-width: 0; min-width: 0;

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUserCircle } from '@fortawesome/free-solid-svg-icons'; import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Dropdown } from '@edx/paragon'; import { Dropdown } from '@edx/paragon';
@@ -18,8 +19,8 @@ const AuthenticatedUserDropdown = ({ intl, username }) => {
return ( return (
<> <>
<a className="text-gray-700" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a> <a className="text-gray-700 mr-3" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
<Dropdown className="user-dropdown ml-3"> <Dropdown className="user-dropdown">
<Dropdown.Toggle variant="outline-primary"> <Dropdown.Toggle variant="outline-primary">
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" /> <FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
<span data-hj-suppress className="d-none d-md-inline"> <span data-hj-suppress className="d-none d-md-inline">

View File

@@ -49,12 +49,12 @@ const LearningHeader = ({
<span className="d-block m-0 font-weight-bold course-title">{courseTitle}</span> <span className="d-block m-0 font-weight-bold course-title">{courseTitle}</span>
</div> </div>
{showUserDropdown && authenticatedUser && ( {showUserDropdown && authenticatedUser && (
<AuthenticatedUserDropdown <AuthenticatedUserDropdown
username={authenticatedUser.username} username={authenticatedUser.username}
/> />
)} )}
{showUserDropdown && !authenticatedUser && ( {showUserDropdown && !authenticatedUser && (
<AnonymousUserMenu /> <AnonymousUserMenu />
)} )}
</div> </div>
</header> </header>

View File

@@ -12,7 +12,7 @@ describe('Header', () => {
it('displays user button', () => { it('displays user button', () => {
render(<Header />); render(<Header />);
expect(screen.getByText(authenticatedUser.username)).toBeInTheDocument(); expect(screen.getByRole('button')).toHaveTextContent(authenticatedUser.username);
}); });
it('displays course data', () => { it('displays course data', () => {

View File

@@ -3,7 +3,7 @@
import Enzyme from 'enzyme'; import Enzyme from 'enzyme';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; import Adapter from 'enzyme-adapter-react-16';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import '@testing-library/jest-dom/extend-expect'; import '@testing-library/jest-dom/extend-expect';
import 'babel-polyfill'; import 'babel-polyfill';

View File

@@ -1,24 +0,0 @@
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

@@ -1,54 +0,0 @@
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

@@ -1,155 +0,0 @@
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

@@ -1,76 +0,0 @@
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

@@ -1,51 +0,0 @@
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

@@ -1,38 +0,0 @@
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

@@ -1,74 +0,0 @@
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

@@ -1,197 +0,0 @@
/* 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

@@ -1,69 +0,0 @@
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

@@ -1,64 +0,0 @@
// 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

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

View File

@@ -1,156 +0,0 @@
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

@@ -1,36 +0,0 @@
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;

View File

@@ -1,6 +0,0 @@
const executeThunk = async (thunk, dispatch, getState) => {
await thunk(dispatch, getState);
await new Promise(setImmediate);
};
export default executeThunk;