- {showLocaleDropdownMenu ?
: null}
{t('footer.tax-exempt-status')}
{t('footer.mission-statement')}
{t('footer.donation-initiatives')}
diff --git a/client/src/components/Header/Header.test.js b/client/src/components/Header/Header.test.js
index 3c2e4de31a..b1d09ffd4a 100644
--- a/client/src/components/Header/Header.test.js
+++ b/client/src/components/Header/Header.test.js
@@ -1,21 +1,23 @@
/* global expect */
import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow';
-/* import { useTranslation } from 'react-i18next';
-import { I18nextProvider } from 'react-i18next';
-import i18n from '../../../i18n/configForTests';*/
import { UniversalNav } from './components/UniversalNav';
import { NavLinks } from './components/NavLinks';
import AuthOrProfile from './components/AuthOrProfile';
+import { apiLocation } from '../../../../config/env.json';
+
describe('
', () => {
const UniversalNavProps = {
displayMenu: false,
menuButtonRef: {},
searchBarRef: {},
toggleDisplayMenu: function() {},
- pathName: '/'
+ pathName: '/',
+ fetchState: {
+ pending: false
+ }
};
it('renders to the DOM', () => {
const shallow = new ShallowRenderer();
@@ -26,14 +28,14 @@ describe('
', () => {
});
describe('
', () => {
- it('has expected navigation links', () => {
+ it('has expected navigation links when not signed in', () => {
const landingPageProps = {
fetchState: {
pending: false
},
user: {
- isUserDonating: false,
- username: '',
+ isDonating: false,
+ username: null,
theme: 'default'
},
i18n: {
@@ -45,11 +47,70 @@ describe('
', () => {
shallow.render(
);
const result = shallow.getRenderOutput();
expect(
- hasRadioNavItem(result) &&
- hasForumNavItem(result) &&
+ hasDonateNavItem(result) &&
+ hasSignInNavItem(result) &&
hasCurriculumNavItem(result) &&
+ hasForumNavItem(result) &&
hasNewsNavItem(result) &&
- hasDonateNavItem(result)
+ hasRadioNavItem(result)
+ ).toBeTruthy();
+ });
+
+ it('has expected navigation links when signed in', () => {
+ const landingPageProps = {
+ fetchState: {
+ pending: false
+ },
+ user: {
+ isDonating: false,
+ username: 'nhcarrigan',
+ theme: 'default'
+ },
+ i18n: {
+ language: 'en'
+ },
+ toggleNightMode: theme => theme
+ };
+ const shallow = new ShallowRenderer();
+ shallow.render(
);
+ const result = shallow.getRenderOutput();
+ expect(
+ hasDonateNavItem(result) &&
+ hasCurriculumNavItem(result) &&
+ hasProfileAndSettingsNavItems(result, landingPageProps.user.username) &&
+ hasForumNavItem(result) &&
+ hasNewsNavItem(result) &&
+ hasRadioNavItem(result) &&
+ hasSignOutNavItem(result)
+ ).toBeTruthy();
+ });
+
+ it('has expected navigation links when signed in and donating', () => {
+ const landingPageProps = {
+ fetchState: {
+ pending: false
+ },
+ user: {
+ isDonating: true,
+ username: 'moT01',
+ theme: 'default'
+ },
+ i18n: {
+ language: 'en'
+ },
+ toggleNightMode: theme => theme
+ };
+ const shallow = new ShallowRenderer();
+ shallow.render(
);
+ const result = shallow.getRenderOutput();
+ expect(
+ hasThanksForDonating(result) &&
+ hasCurriculumNavItem(result) &&
+ hasProfileAndSettingsNavItems(result, landingPageProps.user.username) &&
+ hasForumNavItem(result) &&
+ hasNewsNavItem(result) &&
+ hasRadioNavItem(result) &&
+ hasSignOutNavItem(result)
).toBeTruthy();
});
});
@@ -68,7 +129,6 @@ describe('
', () => {
const shallow = new ShallowRenderer();
shallow.render(
);
const componentTree = shallow.getRenderOutput();
-
expect(avatarHasClass(componentTree, 'default-border')).toBeTruthy();
});
@@ -125,7 +185,7 @@ describe('
', () => {
});
const navigationLinks = (component, navItem) => {
- return component.props.children.props.children[navItem].props.children.props;
+ return component.props.children[navItem].props;
};
const profileNavItem = component => component.props.children;
@@ -135,29 +195,66 @@ const hasDonateNavItem = component => {
return children === 'buttons.donate' && to === '/donate';
};
+const hasThanksForDonating = component => {
+ const { children } = navigationLinks(component, 0);
+ return children[0].props.children === 'donate.thanks';
+};
+
+const hasSignInNavItem = component => {
+ const { children } = navigationLinks(component, 1);
+ return children === 'buttons.sign-in';
+};
+
+const hasCurriculumNavItem = component => {
+ const { children, to } = navigationLinks(component, 2);
+ return children === 'buttons.curriculum' && to === '/learn';
+};
+
+const hasProfileAndSettingsNavItems = (component, username) => {
+ const fragment = navigationLinks(component, 3);
+
+ const profile = fragment.children[0].props;
+ const settings = fragment.children[1].props;
+
+ const hasProfile =
+ profile.children === 'buttons.profile' && profile.to === `/${username}`;
+ const hasSettings =
+ settings.children === 'buttons.settings' && settings.to === '/settings';
+
+ return hasProfile && hasSettings;
+};
+
const hasForumNavItem = component => {
- const { children, to } = navigationLinks(component, 1);
+ const { children, to } = navigationLinks(component, 5);
return (
- children === 'buttons.forum' && to === 'https://forum.freecodecamp.org'
+ children[0].props.children === 'buttons.forum' &&
+ to === 'https://forum.freecodecamp.org/'
);
};
const hasNewsNavItem = component => {
- const { children, to } = navigationLinks(component, 2);
+ const { children, to } = navigationLinks(component, 6);
return (
- children === 'buttons.news' && to === 'https://www.freecodecamp.org/news'
+ children[0].props.children === 'buttons.news' &&
+ to === 'https://www.freecodecamp.org/news'
);
};
-const hasCurriculumNavItem = component => {
- const { children, to } = navigationLinks(component, 3);
- return children === 'buttons.curriculum' && to === '/learn';
+const hasRadioNavItem = component => {
+ const { children, to } = navigationLinks(component, 7);
+ return (
+ children[0].props.children === 'buttons.radio' &&
+ to === 'https://coderadio.freecodecamp.org'
+ );
};
-const hasRadioNavItem = component => {
- const { children, to } = navigationLinks(component, 5);
+const hasSignOutNavItem = component => {
+ const { children } = navigationLinks(component, 10);
+ const signOutProps = children[1].props;
+
return (
- children === 'buttons.radio' && to === 'https://coderadio.freecodecamp.org'
+ signOutProps.children === 'buttons.sign-out' &&
+ signOutProps.href === `${apiLocation}/signout`
);
};
diff --git a/client/src/components/Header/components/NavLinks.js b/client/src/components/Header/components/NavLinks.js
index 879425a225..13777ad3eb 100644
--- a/client/src/components/Header/components/NavLinks.js
+++ b/client/src/components/Header/components/NavLinks.js
@@ -2,30 +2,71 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
-
-import { Link, SkeletonSprite } from '../../helpers';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import {
+ // faCheck,
+ faCheckSquare,
+ faHeart,
+ faSquare,
+ faExternalLinkAlt
+} from '@fortawesome/free-solid-svg-icons';
+import { Link } from '../../helpers';
import { updateUserFlag } from '../../../redux/settings';
import {
clientLocale,
- forumLocation,
radioLocation,
- newsLocation
+ apiLocation
} from '../../../../../config/env.json';
-import createLanguageRedirect from '../../createLanguageRedirect';
+// import createLanguageRedirect from '../../createLanguageRedirect';
+import createExternalRedirect from '../../createExternalRedirects';
-const {
+/* const {
availableLangs,
i18nextCodes,
langDisplayNames
-} = require('../../../../i18n/allLangs');
+} = require('../../../../i18n/allLangs'); */
-const locales = availableLangs.client;
+// const locales = availableLangs.client;
+
+// The linter was complaining about inline comments. Add the code below above
+// the sign out button when the language menu is ready to be added
+/*
+
+ {t('footer.language')}
+
+ {locales.map(lang =>
+ // current lang is a button that closes the menu
+ i18n.language === i18nextCodes[lang] ? (
+
+ ) : (
+
+ {langDisplayNames[lang]}
+
+ )
+ )
+*/
const propTypes = {
displayMenu: PropTypes.bool,
fetchState: PropTypes.shape({ pending: PropTypes.bool }),
i18n: PropTypes.object,
t: PropTypes.func,
+ toggleDisplayMenu: PropTypes.func,
toggleNightMode: PropTypes.func.isRequired,
user: PropTypes.object
};
@@ -36,117 +77,134 @@ const mapDispatchToProps = {
export class NavLinks extends Component {
toggleTheme(currentTheme = 'default', toggleNightMode) {
- console.log('attempting to toggle night mode');
toggleNightMode(currentTheme === 'night' ? 'default' : 'night');
}
render() {
const {
displayMenu,
+ // i18n,
fetchState,
- i18n,
t,
+ // toggleDisplayMenu,
toggleNightMode,
- user: { isUserDonating = false, username, theme }
+ user: { isDonating = false, username, theme }
} = this.props;
const { pending } = fetchState;
-
return pending ? (
-
-
-
+
) : (
-
-
- -
- {isUserDonating ? (
- {t('donate.thanks')}
- ) : (
-
- {t('buttons.donate')}
-
- )}
-
- -
+
+ {isDonating ? (
+
+ {t('donate.thanks')}
+
+
+ ) : (
+
+ {t('buttons.donate')}
+
+ )}
+ {!username && (
+
+ {t('buttons.sign-in')}
+
+ )}
+
+ {t('buttons.curriculum')}
+
+ {username && (
+ <>
- {t('buttons.forum')}
+ {t('buttons.profile')}
-
-
-
- {t('buttons.news')}
+ {t('buttons.settings')}
-
-
-
-
- {t('buttons.curriculum')}
-
-
- {username && (
-
-
-
- {t('buttons.profile')}
-
-
+ >
+ )}
+
+
+
{t('buttons.forum')}
+
+
+
+
{t('buttons.news')}
+
+
+
+
{t('buttons.radio')}
+
+
+
+
+ {username && (
+ <>
+
+
+ {t('buttons.sign-out')}
+
+ >
+ )}
);
}
diff --git a/client/src/components/Header/components/UniversalNav.js b/client/src/components/Header/components/UniversalNav.js
index 9b0388d683..47f5c86bbe 100644
--- a/client/src/components/Header/components/UniversalNav.js
+++ b/client/src/components/Header/components/UniversalNav.js
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Link } from '../../helpers';
+import { Link, SkeletonSprite } from '../../helpers';
import NavLogo from './NavLogo';
import SearchBar from '../../search/searchBar/SearchBar';
import MenuButton from './MenuButton';
@@ -15,33 +15,50 @@ export const UniversalNav = ({
searchBarRef,
user,
fetchState
-}) => (
-
-
-
-);
+
+ );
+};
UniversalNav.displayName = 'UniversalNav';
export default UniversalNav;
diff --git a/client/src/components/Header/components/universalNav.css b/client/src/components/Header/components/universalNav.css
index 7879081929..fb6e1948d3 100644
--- a/client/src/components/Header/components/universalNav.css
+++ b/client/src/components/Header/components/universalNav.css
@@ -66,6 +66,7 @@
text-decoration: none;
background-color: var(--theme-color);
}
+
#universal-nav-logo:focus {
background-color: inherit;
}
@@ -90,19 +91,14 @@
margin: 0 0 0 -12px;
padding: 0;
list-style: none;
- max-width: 300px;
-}
-
-.nav-list li {
- display: block;
- margin: 0;
- padding: 0;
+ max-width: 250px;
}
.nav-link {
+ margin: 0;
+ padding: 2px 15px 0 15px;
display: flex;
align-items: center;
- padding: 2px 15px 0 25px;
color: var(--gray-00);
background-color: var(--gray-90);
opacity: 1;
@@ -113,42 +109,47 @@
border: none;
}
-.nav-list span.nav-link {
- padding: 2px 15px 0 15px;
- background-color: var(--gray-75);
-}
-
-.nav-list a.nav-link:hover,
-.nav-list button.nav-link:hover {
+.nav-link:hover,
+.nav-link:active {
color: var(--theme-color);
text-decoration: none;
background: white;
+ cursor: pointer;
}
-.nav-list button:disabled {
- background-color: var(--gray-75);
+.nav-link-header,
+.nav-link-header:hover,
+.nav-link-header:active {
+ color: var(--gray-00);
+ background-color: var(--gray-90);
+ cursor: default;
}
-.nav-list button:disabled:hover {
- color: white;
- background-color: var(--gray-75);
+.nav-link .fa-external-link-alt {
+ color: var(--gray-45);
}
-.nav-list li a.nav-link:focus {
- background: var(--theme-color);
- color: white;
+.nav-link .fa-check,
+.nav-link .fa-check-square {
+ width: 18px !important;
+ height: auto !important;
}
-.nav-list li a.nav-link:focus:hover {
- color: var(--theme-color);
- background: white;
+.nav-link-lang {
+ padding-left: 30px;
}
-.nav-list a.btn-cta {
- padding: 0 20px;
- height: 30px;
- margin-left: 19px;
- margin-right: 15px;
+.nav-link-flex {
+ display: flex;
+ justify-content: space-between;
+}
+
+.nav-link-sign-in {
+ display: none;
+}
+
+.nav-link-dull {
+ color: var(--gray-45);
}
.nav-skeleton {
@@ -247,6 +248,23 @@
color: var(--theme-color);
}
+.nav-line,
+.nav-line-2 {
+ border-color: var(--gray-45);
+ width: 100%;
+ margin: 0;
+}
+
+.nav-line-2 {
+ border-top-width: 2px;
+}
+
+.signup-btn {
+ max-height: calc(var(--header-height) - 6px);
+ padding: 0 8px;
+ margin-left: 2px;
+}
+
@media (max-width: 980px) {
.universal-nav-left {
display: none;
@@ -306,7 +324,7 @@
}
}
-@media (max-width: 400px) {
+@media (max-width: 455px) {
.universal-nav {
padding: 0 5px;
}
@@ -315,14 +333,26 @@
padding: 0 5px;
}
+ .nav-link-sign-in {
+ display: flex;
+ }
+
+ .navatar .signup-btn {
+ display: none;
+ }
+
+ .navatar {
+ display: none;
+ }
+
#universal-nav-logo {
- left: 5px;
- max-width: 45%;
+ max-width: 60%;
}
}
-.signup-btn {
- max-height: calc(var(--header-height) - 6px);
- padding: 0 4px;
- margin-left: 2px;
+@media (max-width: 300px) {
+ #universal-nav-logo {
+ max-width: none;
+ left: -170px;
+ }
}
diff --git a/client/src/components/createExternalRedirects.js b/client/src/components/createExternalRedirects.js
new file mode 100644
index 0000000000..bb8480689a
--- /dev/null
+++ b/client/src/components/createExternalRedirects.js
@@ -0,0 +1,16 @@
+import { forumLocation } from '../../config/env.json';
+
+const createExternalRedirect = (page, { clientLocale }) => {
+ const isNotEnglish = clientLocale !== 'english';
+ if (clientLocale === 'chinese') {
+ return `https://chinese.freecodecamp.org/${page}`;
+ }
+ if (page === 'forum') {
+ return `${forumLocation}/${isNotEnglish ? 'c/' + clientLocale + '/' : ''}`;
+ }
+ return `https://www.freecodecamp.org/${
+ isNotEnglish ? clientLocale + '/news' : 'news'
+ }`;
+};
+
+export default createExternalRedirect;
diff --git a/client/src/components/createExternalRedirects.test.js b/client/src/components/createExternalRedirects.test.js
new file mode 100644
index 0000000000..4b966e985d
--- /dev/null
+++ b/client/src/components/createExternalRedirects.test.js
@@ -0,0 +1,81 @@
+/* global expect */
+
+import createExternalRedirect from './createExternalRedirects';
+
+describe('createExternalRedirects', () => {
+ describe('english redirects', () => {
+ const envVars = {
+ clientLocale: 'english'
+ };
+
+ const englishForumUrl = 'https://forum.freecodecamp.org/';
+ const englishNewsUrl = 'https://www.freecodecamp.org/news';
+
+ it('should generate correct forum link', () => {
+ const receivedUrl = createExternalRedirect('forum', { ...envVars });
+ expect(receivedUrl).toBe(englishForumUrl);
+ });
+
+ it('should generate correct news link', () => {
+ const receivedUrl = createExternalRedirect('news', { ...envVars });
+ expect(receivedUrl).toBe(englishNewsUrl);
+ });
+ });
+
+ describe('chinese redirects', () => {
+ const envVars = {
+ clientLocale: 'chinese'
+ };
+
+ const englishForumUrl = 'https://chinese.freecodecamp.org/forum';
+ const englishNewsUrl = 'https://chinese.freecodecamp.org/news';
+
+ it('should generate correct forum link', () => {
+ const receivedUrl = createExternalRedirect('forum', { ...envVars });
+ expect(receivedUrl).toBe(englishForumUrl);
+ });
+
+ it('should generate correct news link', () => {
+ const receivedUrl = createExternalRedirect('news', { ...envVars });
+ expect(receivedUrl).toBe(englishNewsUrl);
+ });
+ });
+
+ describe('spanish redirects', () => {
+ const envVars = {
+ clientLocale: 'espanol'
+ };
+
+ const englishForumUrl = 'https://forum.freecodecamp.org/c/espanol/';
+ const englishNewsUrl = 'https://www.freecodecamp.org/espanol/news';
+
+ it('should generate correct forum link', () => {
+ const receivedUrl = createExternalRedirect('forum', { ...envVars });
+ expect(receivedUrl).toBe(englishForumUrl);
+ });
+
+ it('should generate correct news link', () => {
+ const receivedUrl = createExternalRedirect('news', { ...envVars });
+ expect(receivedUrl).toBe(englishNewsUrl);
+ });
+ });
+
+ describe('french redirects', () => {
+ const envVars = {
+ clientLocale: 'francais'
+ };
+
+ const englishForumUrl = 'https://forum.freecodecamp.org/c/francais/';
+ const englishNewsUrl = 'https://www.freecodecamp.org/francais/news';
+
+ it('should generate correct forum link', () => {
+ const receivedUrl = createExternalRedirect('forum', { ...envVars });
+ expect(receivedUrl).toBe(englishForumUrl);
+ });
+
+ it('should generate correct news link', () => {
+ const receivedUrl = createExternalRedirect('news', { ...envVars });
+ expect(receivedUrl).toBe(englishNewsUrl);
+ });
+ });
+});
diff --git a/sample.env b/sample.env
index a93cc53c74..c8e000a9e7 100644
--- a/sample.env
+++ b/sample.env
@@ -54,7 +54,6 @@ FREECODECAMP_NODE_ENV='development'
# Languages to build
CLIENT_LOCALE=english
CURRICULUM_LOCALE=english
-SHOW_LOCALE_DROPDOWN_MENU=true
# Show or hide WIP in progress challenges
SHOW_UPCOMING_CHANGES=false