diff --git a/package-lock.json b/package-lock.json index 89847a22d..44fcbe9d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "babel-polyfill": "6.26.0", "jest-environment-jsdom": "^29.7.0", "react-responsive": "8.2.0", - "react-transition-group": "4.4.5" + "react-transition-group": "4.4.5", + "universal-cookie": "^7.2.0" }, "devDependencies": { "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", @@ -2254,6 +2255,12 @@ "redux": "^4.0.4" } }, + "node_modules/@edx/frontend-platform/node_modules/@types/cookie": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", + "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==", + "dev": true + }, "node_modules/@edx/frontend-platform/node_modules/axios": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.28.1.tgz", @@ -2266,6 +2273,25 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/@edx/frontend-platform/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@edx/frontend-platform/node_modules/universal-cookie": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", + "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.3.3", + "cookie": "^0.4.0" + } + }, "node_modules/@edx/new-relic-source-map-webpack-plugin": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@edx/new-relic-source-map-webpack-plugin/-/new-relic-source-map-webpack-plugin-2.1.0.tgz", @@ -4455,10 +4481,9 @@ } }, "node_modules/@types/cookie": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", - "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==", - "dev": true + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, "node_modules/@types/eslint": { "version": "8.56.10", @@ -7008,7 +7033,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -18065,22 +18089,12 @@ } }, "node_modules/universal-cookie": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", - "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", - "dev": true, + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.0.tgz", + "integrity": "sha512-PvcyflJAYACJKr28HABxkGemML5vafHmiL4ICe3e+BEKXRMt0GaFLZhAwgv637kFFnnfiSJ8e6jknrKkMrU+PQ==", "dependencies": { - "@types/cookie": "^0.3.3", - "cookie": "^0.4.0" - } - }, - "node_modules/universal-cookie/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0" } }, "node_modules/universalify": { diff --git a/package.json b/package.json index e55e2f6c2..82fd52d4b 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,8 @@ "babel-polyfill": "6.26.0", "jest-environment-jsdom": "^29.7.0", "react-responsive": "8.2.0", - "react-transition-group": "4.4.5" + "react-transition-group": "4.4.5", + "universal-cookie": "^7.2.0" }, "peerDependencies": { "@edx/frontend-platform": "^7.0.0 || ^8.0.0", diff --git a/src/DesktopHeader.jsx b/src/DesktopHeader.jsx index 05cc9372d..d915f7812 100644 --- a/src/DesktopHeader.jsx +++ b/src/DesktopHeader.jsx @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { getConfig } from '@edx/frontend-platform'; +import ThemeToggleButton from './ThemeToggleButton'; // Local Components import { Menu, MenuTrigger, MenuContent } from './Menu'; @@ -156,6 +157,7 @@ class DesktopHeader extends React.Component { aria-label={intl.formatMessage(messages['header.label.secondary.nav'])} className="nav secondary-menu-container align-items-center ml-auto" > + {loggedIn ? ( <> diff --git a/src/MobileHeader.jsx b/src/MobileHeader.jsx index 767e0c8c7..e26c539de 100644 --- a/src/MobileHeader.jsx +++ b/src/MobileHeader.jsx @@ -6,6 +6,7 @@ import { getConfig } from '@edx/frontend-platform'; // Local Components import { Menu, MenuTrigger, MenuContent } from './Menu'; import { LinkedLogo, Logo } from './Logo'; +import ThemeToggleButton from './ThemeToggleButton'; // i18n import messages from './Header.messages'; @@ -154,6 +155,7 @@ class MobileHeader extends React.Component {
{ logoDestination === null ? : }
+ {userMenu.length > 0 || loggedOutItems.length > 0 ? (
diff --git a/src/ThemeToggleButton.jsx b/src/ThemeToggleButton.jsx new file mode 100644 index 000000000..862e4d01e --- /dev/null +++ b/src/ThemeToggleButton.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { getConfig } from '@edx/frontend-platform'; +import Cookies from 'universal-cookie'; + +const ThemeToggleButton = () => { + const getNextWeek = () => { + const today = new Date(); + return new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7); + }; + + const onToggleTheme = () => { + const cookies = new Cookies(); + const serverURL = new URL(getConfig().LMS_BASE_URL); + const themeCookieName = getConfig().THEME_COOKIE_NAME; + + const options = { domain: serverURL.hostname, path: '/', expires: getNextWeek() }; + let themeName = ''; + + if (cookies.get(themeCookieName) === 'dark') { + document.body.classList.remove('indigo-dark-theme'); + themeName = 'light'; + } else { + document.body.classList.add('indigo-dark-theme'); + themeName = 'dark'; + } + cookies.set(themeCookieName, themeName, options); + }; + + return ( +
+ +
+ + + + + + + + +
+
+
+ ); +}; + +export default ThemeToggleButton; diff --git a/src/__snapshots__/Header.test.jsx.snap b/src/__snapshots__/Header.test.jsx.snap index c34c5706e..eb320bef2 100644 --- a/src/__snapshots__/Header.test.jsx.snap +++ b/src/__snapshots__/Header.test.jsx.snap @@ -42,6 +42,46 @@ exports[`
renders correctly for anonymous desktop 1`] = ` aria-label="Secondary" className="nav secondary-menu-container align-items-center ml-auto" > +
+ +
+ + + + + + + + +
+
+
renders correctly for anonymous mobile 1`] = ` />
+
+ +
+ + + + + + + + +
+
+
@@ -225,6 +305,46 @@ exports[`
renders correctly for authenticated desktop 1`] = ` aria-label="Secondary" className="nav secondary-menu-container align-items-center ml-auto" > +
+ +
+ + + + + + + + +
+
+
renders correctly for authenticated mobile 1`] = ` />
+
+ +
+ + + + + + + + +
+
+
diff --git a/src/learning-header/LearningHeader.jsx b/src/learning-header/LearningHeader.jsx index 576aa6768..dccccefe1 100644 --- a/src/learning-header/LearningHeader.jsx +++ b/src/learning-header/LearningHeader.jsx @@ -6,6 +6,7 @@ import { AppContext } from '@edx/frontend-platform/react'; import AnonymousUserMenu from './AnonymousUserMenu'; import AuthenticatedUserDropdown from './AuthenticatedUserDropdown'; +import ThemeToggleButton from '../ThemeToggleButton'; import messages from './messages'; const LinkedLogo = ({ @@ -60,6 +61,7 @@ const LearningHeader = ({
+ {showUserDropdown && authenticatedUser && (