diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 499db8c900..994a5308d3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,8 +2,8 @@ name: Release CI on: push: branches: - - master - - alpha + - nau/*.master + jobs: release: name: Release @@ -38,10 +38,7 @@ jobs: fail_ci_if_error: false - name: Build run: npm run build - - name: Release - uses: cycjimmy/semantic-release-action@v3 + - name: Release to npmjs + uses: JS-DevTools/npm-publish@v3 with: - semantic_version: 16 - env: - GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_NPM_TOKEN }} + token: ${{ secrets.NPM_AUTH_TOKEN }} diff --git a/README.rst b/README.rst index d7b6d50724..577497d0f8 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,10 @@ Environment Variables * ``AUTHN_MINIMAL_HEADER`` - A boolean flag which hides the main menu, user menu, and logged-out 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. +* ``ENABLE_HEADER_LANG_SELECTOR`` - A boolean to enable the language selector in the header component. +* ``SITE_SUPPORTED_LANGUAGES`` - A list with all the languages to display in the selector. +* ``LOGO_URL_MOBILE`` - The URL of the site's logo for mobile. This logo is displayed in the header. + Installation ============ diff --git a/package.json b/package.json index 2f0fbce9ba..9503804bcf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@edx/frontend-component-header", - "version": "1.0.0-semantically-released", - "description": "The standard header for Open edX", + "name": "@nauedu/frontend-component-header", + "version": "20.1.0", + "description": "NAU header component for Open edX", "main": "dist/index.js", "publishConfig": { "access": "public" @@ -22,14 +22,14 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/openedx/frontend-component-header.git" + "url": "git+https://github.com/fccn/frontend-component-header-nau.git" }, "author": "edX", "license": "AGPL-3.0", "bugs": { - "url": "https://github.com/openedx/frontend-component-header/issues" + "url": "https://github.com/fccn/frontend-component-header-nau/issues" }, - "homepage": "https://github.com/openedx/frontend-component-header#readme", + "homepage": "https://github.com/fccn/frontend-component-header-nau#readme", "devDependencies": { "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", "@edx/browserslist-config": "^1.1.1", diff --git a/src/Header.jsx b/src/Header.jsx index 2f190f205a..8027d1da91 100644 --- a/src/Header.jsx +++ b/src/Header.jsx @@ -3,7 +3,7 @@ import Responsive from 'react-responsive'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { AppContext } from '@edx/frontend-platform/react'; import { - APP_CONFIG_INITIALIZED, + APP_PUBSUB_INITIALIZED, ensureConfig, mergeConfig, getConfig, @@ -25,9 +25,10 @@ ensureConfig([ 'ORDER_HISTORY_URL', ], 'Header component'); -subscribe(APP_CONFIG_INITIALIZED, () => { +subscribe(APP_PUBSUB_INITIALIZED, () => { mergeConfig({ AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER, + ENABLE_ORG_LOGO: !!process.env.ENABLE_ORG_LOGO, }, 'Header additional config'); }); diff --git a/src/LanguageSelector/data/api.js b/src/LanguageSelector/data/api.js new file mode 100644 index 0000000000..0ec4200075 --- /dev/null +++ b/src/LanguageSelector/data/api.js @@ -0,0 +1,32 @@ +import { getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { convertKeyNames, snakeCaseObject } from '@edx/frontend-platform/utils'; + +export async function patchPreferences(username, params) { + let processedParams = snakeCaseObject(params); + processedParams = convertKeyNames(processedParams, { + pref_lang: 'pref-lang', + }); + + await getAuthenticatedHttpClient() + .patch(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`, processedParams, { + headers: { 'Content-Type': 'application/merge-patch+json' }, + }); + + return params; +} + +export async function postSetLang(code) { + const formData = new FormData(); + const requestConfig = { + headers: { + Accept: 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + }, + }; + const url = `${getConfig().LMS_BASE_URL}/i18n/setlang/`; + formData.append('language', code); + + await getAuthenticatedHttpClient() + .post(url, formData, requestConfig); +} diff --git a/src/LanguageSelector/index.jsx b/src/LanguageSelector/index.jsx new file mode 100644 index 0000000000..4afa67275a --- /dev/null +++ b/src/LanguageSelector/index.jsx @@ -0,0 +1,98 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faGlobe } from '@fortawesome/free-solid-svg-icons'; +import { publish } from '@edx/frontend-platform'; +import { + getLocale, injectIntl, intlShape, FormattedMessage, LOCALE_CHANGED, handleRtl, +} from '@edx/frontend-platform/i18n'; +import { Dropdown } from '@openedx/paragon'; +import { logError } from '@edx/frontend-platform/logging'; + +import { patchPreferences, postSetLang } from './data/api'; + +const onLanguageSelected = async (username, selectedLanguageCode) => { + try { + if (username) { + await patchPreferences(username, { prefLang: selectedLanguageCode }); + await postSetLang(selectedLanguageCode); + } + publish(LOCALE_CHANGED, getLocale()); + handleRtl(); + } catch (error) { + logError(error); + } +}; + +const LanguageSelector = ({ + intl, options, authenticatedUser, compact, +}) => { + const languageLabel = (languageCode) => { + const option = options.find(({ value }) => value === languageCode); + return option ? option.label : null; + }; + + const handleChange = (languageCode, event) => { + const previousSiteLanguage = getLocale(); + /* eslint-disable no-console */ + console.debug(previousSiteLanguage, languageCode, authenticatedUser); + + if (previousSiteLanguage !== languageCode) { + onLanguageSelected(authenticatedUser?.username, languageCode); + } + + const languageLabelElement = event.target.parentElement.parentElement.querySelector('.languageLabel'); + languageLabelElement.innerHTML = languageLabel(languageCode); + }; + + const currentLangLabel = languageLabel(intl.locale); + const showLabel = !(compact || false); + + return ( + + + + {showLabel && ( + currentLangLabel ? ( + + {currentLangLabel} + + ) : ( + + + + ) + )} + + + {options.map(({ value, label }) => ( + + {label} + + ))} + + + ); +}; + +LanguageSelector.propTypes = { + authenticatedUser: PropTypes.shape({ + username: PropTypes.string, + }).isRequired, + intl: intlShape.isRequired, + compact: PropTypes.bool, + options: PropTypes.arrayOf(PropTypes.shape({ + value: PropTypes.string, + label: PropTypes.string, + })).isRequired, +}; + +LanguageSelector.defaultProps = { + compact: false, +}; + +export default injectIntl(LanguageSelector); diff --git a/src/__snapshots__/Header.test.jsx.snap b/src/__snapshots__/Header.test.jsx.snap index 781e7f729d..a39ad69870 100644 --- a/src/__snapshots__/Header.test.jsx.snap +++ b/src/__snapshots__/Header.test.jsx.snap @@ -28,7 +28,7 @@ exports[`
renders correctly for anonymous desktop 1`] = `