feat(client): redesigned navigation (#40709)
Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
9014fff6c4
commit
58c6c54c67
@ -55,7 +55,10 @@
|
|||||||
"sign-out": "退出",
|
"sign-out": "退出",
|
||||||
"curriculum": "课程",
|
"curriculum": "课程",
|
||||||
"forum": "论坛",
|
"forum": "论坛",
|
||||||
|
"radio": "Radio",
|
||||||
"profile": "个人资料",
|
"profile": "个人资料",
|
||||||
|
"news": "News",
|
||||||
|
"donate": "Donate",
|
||||||
"update-settings": "更新我的账号设置",
|
"update-settings": "更新我的账号设置",
|
||||||
"sign-me-out": "退出登录 freeCodeCamp",
|
"sign-me-out": "退出登录 freeCodeCamp",
|
||||||
"flag-user": "标记该用户的账号为滥用",
|
"flag-user": "标记该用户的账号为滥用",
|
||||||
@ -314,6 +317,7 @@
|
|||||||
"donate": {
|
"donate": {
|
||||||
"title": "支持我们的非营利组织",
|
"title": "支持我们的非营利组织",
|
||||||
"processing": "我们正在处理你的捐款。",
|
"processing": "我们正在处理你的捐款。",
|
||||||
|
"thanks": "Thanks for donating",
|
||||||
"thank-you": "谢谢你成为我们的支持者。",
|
"thank-you": "谢谢你成为我们的支持者。",
|
||||||
"thank-you-2": "谢谢你成为 freeCodeCamp 的支持者。现在你已设置定期捐款。",
|
"thank-you-2": "谢谢你成为 freeCodeCamp 的支持者。现在你已设置定期捐款。",
|
||||||
"additional": "你可以使用这个链接 <0>{{url}}</0> 额外进行一次性捐款:",
|
"additional": "你可以使用这个链接 <0>{{url}}</0> 额外进行一次性捐款:",
|
||||||
|
@ -57,7 +57,10 @@
|
|||||||
"sign-out": "Sign out",
|
"sign-out": "Sign out",
|
||||||
"curriculum": "Curriculum",
|
"curriculum": "Curriculum",
|
||||||
"forum": "Forum",
|
"forum": "Forum",
|
||||||
|
"radio": "Radio",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
|
"news": "News",
|
||||||
|
"donate": "Donate",
|
||||||
"update-settings": "Update my account settings",
|
"update-settings": "Update my account settings",
|
||||||
"sign-me-out": "Sign me out of freeCodeCamp",
|
"sign-me-out": "Sign me out of freeCodeCamp",
|
||||||
"flag-user": "Flag This User's Account for Abuse",
|
"flag-user": "Flag This User's Account for Abuse",
|
||||||
@ -316,6 +319,7 @@
|
|||||||
"donate": {
|
"donate": {
|
||||||
"title": "Support our nonprofit",
|
"title": "Support our nonprofit",
|
||||||
"processing": "We are processing your donation.",
|
"processing": "We are processing your donation.",
|
||||||
|
"thanks": "Thanks for donating",
|
||||||
"thank-you": "Thank you for being a supporter.",
|
"thank-you": "Thank you for being a supporter.",
|
||||||
"thank-you-2": "Thank you for being a supporter of freeCodeCamp. You currently have a recurring donation.",
|
"thank-you-2": "Thank you for being a supporter of freeCodeCamp. You currently have a recurring donation.",
|
||||||
"additional": "You can make an additional one-time donation of any amount using this link: <0>{{url}}</0>",
|
"additional": "You can make an additional one-time donation of any amount using this link: <0>{{url}}</0>",
|
||||||
@ -376,7 +380,7 @@
|
|||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"label": "Search",
|
"label": "Search",
|
||||||
"placeholder": "Search 6,000+ tutorial",
|
"placeholder": "Search 6,000+ tutorials",
|
||||||
"see-results": "See all results for {{searchQuery}}",
|
"see-results": "See all results for {{searchQuery}}",
|
||||||
"no-tutorials": "No tutorials found",
|
"no-tutorials": "No tutorials found",
|
||||||
"try": "Looking for something? Try the search bar on this page.",
|
"try": "Looking for something? Try the search bar on this page.",
|
||||||
|
@ -57,7 +57,10 @@
|
|||||||
"sign-out": "Cerrar sesión",
|
"sign-out": "Cerrar sesión",
|
||||||
"curriculum": "Plan de estudio",
|
"curriculum": "Plan de estudio",
|
||||||
"forum": "Foro",
|
"forum": "Foro",
|
||||||
|
"radio": "Radio",
|
||||||
"profile": "Perfil",
|
"profile": "Perfil",
|
||||||
|
"news": "News",
|
||||||
|
"donate": "Donate",
|
||||||
"update-settings": "Actualizar la configuración de mi cuenta",
|
"update-settings": "Actualizar la configuración de mi cuenta",
|
||||||
"sign-me-out": "Cerrar sesión en freeCodeCamp",
|
"sign-me-out": "Cerrar sesión en freeCodeCamp",
|
||||||
"flag-user": "Marcar la cuenta de este usuario por abuso",
|
"flag-user": "Marcar la cuenta de este usuario por abuso",
|
||||||
@ -316,6 +319,7 @@
|
|||||||
"donate": {
|
"donate": {
|
||||||
"title": "Apoya a nuestra organización sin fines de lucro",
|
"title": "Apoya a nuestra organización sin fines de lucro",
|
||||||
"processing": "Estamos procesando tu donación.",
|
"processing": "Estamos procesando tu donación.",
|
||||||
|
"thanks": "Thanks for donating",
|
||||||
"thank-you": "Gracias por tu apoyo.",
|
"thank-you": "Gracias por tu apoyo.",
|
||||||
"thank-you-2": "Gracias por apoyar a freeCodeCamp. Actualmente tienes una donación recurrente.",
|
"thank-you-2": "Gracias por apoyar a freeCodeCamp. Actualmente tienes una donación recurrente.",
|
||||||
"additional": "Puede hacer una donación adicional por única vez de cualquier monto utilizando este enlace: <0>{{url}}</0>",
|
"additional": "Puede hacer una donación adicional por única vez de cualquier monto utilizando este enlace: <0>{{url}}</0>",
|
||||||
|
@ -61,7 +61,10 @@ const translationsSchema = {
|
|||||||
'sign-out': 'Sign out',
|
'sign-out': 'Sign out',
|
||||||
curriculum: 'Curriculum',
|
curriculum: 'Curriculum',
|
||||||
forum: 'Forum',
|
forum: 'Forum',
|
||||||
|
radio: 'Radio',
|
||||||
profile: 'Profile',
|
profile: 'Profile',
|
||||||
|
news: 'News',
|
||||||
|
donate: 'Donate',
|
||||||
'update-settings': 'Update my account settings',
|
'update-settings': 'Update my account settings',
|
||||||
'sign-me-out': 'Sign me out of freeCodeCamp',
|
'sign-me-out': 'Sign me out of freeCodeCamp',
|
||||||
'flag-user': "Flag This User's Account for Abuse",
|
'flag-user': "Flag This User's Account for Abuse",
|
||||||
@ -365,6 +368,7 @@ const translationsSchema = {
|
|||||||
donate: {
|
donate: {
|
||||||
title: 'Support our nonprofit',
|
title: 'Support our nonprofit',
|
||||||
processing: 'We are processing your donation.',
|
processing: 'We are processing your donation.',
|
||||||
|
thanks: 'Thanks for donating',
|
||||||
'thank-you': 'Thank you for being a supporter.',
|
'thank-you': 'Thank you for being a supporter.',
|
||||||
'thank-you-2':
|
'thank-you-2':
|
||||||
'Thank you for being a supporter of freeCodeCamp. You currently have a recurring donation.',
|
'Thank you for being a supporter of freeCodeCamp. You currently have a recurring donation.',
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
/* global expect */
|
/* global expect */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ShallowRenderer from 'react-test-renderer/shallow';
|
import ShallowRenderer from 'react-test-renderer/shallow';
|
||||||
import renderer from 'react-test-renderer';
|
|
||||||
/* import { useTranslation } from 'react-i18next';
|
/* import { useTranslation } from 'react-i18next';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
|
|
||||||
import i18n from '../../../i18n/configForTests';*/
|
import i18n from '../../../i18n/configForTests';*/
|
||||||
import { UniversalNav } from './components/UniversalNav';
|
import { UniversalNav } from './components/UniversalNav';
|
||||||
import { AuthOrProfile } from './components/NavLinks';
|
import { NavLinks } from './components/NavLinks';
|
||||||
|
import AuthOrProfile from './components/AuthOrProfile';
|
||||||
|
|
||||||
describe('<UniversalNav />', () => {
|
describe('<UniversalNav />', () => {
|
||||||
const UniversalNavProps = {
|
const UniversalNavProps = {
|
||||||
@ -26,32 +26,48 @@ describe('<UniversalNav />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('<NavLinks />', () => {
|
describe('<NavLinks />', () => {
|
||||||
it('shows Curriculum and Sign In buttons when not signed in', () => {
|
it('has expected navigation links', () => {
|
||||||
const landingPageProps = {
|
const landingPageProps = {
|
||||||
|
fetchState: {
|
||||||
pending: false
|
pending: false
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
isUserDonating: false,
|
||||||
|
username: '',
|
||||||
|
theme: 'default'
|
||||||
|
},
|
||||||
|
i18n: {
|
||||||
|
language: 'en'
|
||||||
|
},
|
||||||
|
toggleNightMode: theme => theme
|
||||||
};
|
};
|
||||||
const shallow = new ShallowRenderer();
|
const shallow = new ShallowRenderer();
|
||||||
shallow.render(<AuthOrProfile {...landingPageProps} />);
|
shallow.render(<NavLinks {...landingPageProps} />);
|
||||||
const result = shallow.getRenderOutput();
|
const result = shallow.getRenderOutput();
|
||||||
expect(
|
expect(
|
||||||
|
hasRadioNavItem(result) &&
|
||||||
hasForumNavItem(result) &&
|
hasForumNavItem(result) &&
|
||||||
hasCurriculumNavItem(result) &&
|
hasCurriculumNavItem(result) &&
|
||||||
hasSignInButton(result)
|
hasNewsNavItem(result) &&
|
||||||
|
hasDonateNavItem(result)
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<AuthOrProfile />', () => {
|
||||||
it('has avatar with default border for default users', () => {
|
it('has avatar with default border for default users', () => {
|
||||||
const defaultUserProps = {
|
const defaultUserProps = {
|
||||||
user: {
|
user: {
|
||||||
username: 'test-user',
|
username: 'test-user',
|
||||||
picture: 'https://freecodecamp.org/image.png'
|
picture: 'https://freecodecamp.org/image.png'
|
||||||
},
|
},
|
||||||
pending: false
|
pending: false,
|
||||||
|
pathName: '/learn'
|
||||||
};
|
};
|
||||||
|
|
||||||
const componentTree = renderer
|
const shallow = new ShallowRenderer();
|
||||||
.create(<AuthOrProfile {...defaultUserProps} />)
|
shallow.render(<AuthOrProfile {...defaultUserProps} />);
|
||||||
.toJSON();
|
const componentTree = shallow.getRenderOutput();
|
||||||
|
|
||||||
expect(avatarHasClass(componentTree, 'default-border')).toBeTruthy();
|
expect(avatarHasClass(componentTree, 'default-border')).toBeTruthy();
|
||||||
});
|
});
|
||||||
@ -63,11 +79,12 @@ describe('<NavLinks />', () => {
|
|||||||
picture: 'https://freecodecamp.org/image.png',
|
picture: 'https://freecodecamp.org/image.png',
|
||||||
isDonating: true
|
isDonating: true
|
||||||
},
|
},
|
||||||
pending: false
|
pending: false,
|
||||||
|
pathName: '/learn'
|
||||||
};
|
};
|
||||||
const componentTree = renderer
|
const shallow = new ShallowRenderer();
|
||||||
.create(<AuthOrProfile {...donatingUserProps} />)
|
shallow.render(<AuthOrProfile {...donatingUserProps} />);
|
||||||
.toJSON();
|
const componentTree = shallow.getRenderOutput();
|
||||||
|
|
||||||
expect(avatarHasClass(componentTree, 'gold-border')).toBeTruthy();
|
expect(avatarHasClass(componentTree, 'gold-border')).toBeTruthy();
|
||||||
});
|
});
|
||||||
@ -79,12 +96,13 @@ describe('<NavLinks />', () => {
|
|||||||
picture: 'https://freecodecamp.org/image.png',
|
picture: 'https://freecodecamp.org/image.png',
|
||||||
yearsTopContributor: [2020]
|
yearsTopContributor: [2020]
|
||||||
},
|
},
|
||||||
pending: false
|
pending: false,
|
||||||
|
pathName: '/learn'
|
||||||
};
|
};
|
||||||
|
|
||||||
const componentTree = renderer
|
const shallow = new ShallowRenderer();
|
||||||
.create(<AuthOrProfile {...topContributorUserProps} />)
|
shallow.render(<AuthOrProfile {...topContributorUserProps} />);
|
||||||
.toJSON();
|
const componentTree = shallow.getRenderOutput();
|
||||||
|
|
||||||
expect(avatarHasClass(componentTree, 'blue-border')).toBeTruthy();
|
expect(avatarHasClass(componentTree, 'blue-border')).toBeTruthy();
|
||||||
});
|
});
|
||||||
@ -96,41 +114,60 @@ describe('<NavLinks />', () => {
|
|||||||
isDonating: true,
|
isDonating: true,
|
||||||
yearsTopContributor: [2020]
|
yearsTopContributor: [2020]
|
||||||
},
|
},
|
||||||
pending: false
|
pending: false,
|
||||||
|
pathName: '/learn'
|
||||||
};
|
};
|
||||||
const componentTree = renderer
|
const shallow = new ShallowRenderer();
|
||||||
.create(<AuthOrProfile {...topDonatingContributorUserProps} />)
|
shallow.render(<AuthOrProfile {...topDonatingContributorUserProps} />);
|
||||||
.toJSON();
|
const componentTree = shallow.getRenderOutput();
|
||||||
expect(avatarHasClass(componentTree, 'purple-border')).toBeTruthy();
|
expect(avatarHasClass(componentTree, 'purple-border')).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const navigationLinks = (component, navItem) => {
|
const navigationLinks = (component, navItem) => {
|
||||||
return component.props.children[0].props.children[navItem].props.children
|
return component.props.children.props.children[navItem].props.children.props;
|
||||||
.props;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const profileNavItem = component => component[2].children[0];
|
const profileNavItem = component => component.props.children;
|
||||||
|
|
||||||
|
const hasDonateNavItem = component => {
|
||||||
|
const { children, to } = navigationLinks(component, 0);
|
||||||
|
return children === 'buttons.donate' && to === '/donate';
|
||||||
|
};
|
||||||
|
|
||||||
const hasForumNavItem = component => {
|
const hasForumNavItem = component => {
|
||||||
const { children, to } = navigationLinks(component, 0);
|
const { children, to } = navigationLinks(component, 1);
|
||||||
return (
|
return (
|
||||||
children === 'buttons.forum' && to === 'https://forum.freecodecamp.org'
|
children === 'buttons.forum' && to === 'https://forum.freecodecamp.org'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasNewsNavItem = component => {
|
||||||
|
const { children, to } = navigationLinks(component, 2);
|
||||||
|
return (
|
||||||
|
children === 'buttons.news' && to === 'https://www.freecodecamp.org/news'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const hasCurriculumNavItem = component => {
|
const hasCurriculumNavItem = component => {
|
||||||
const { children, to } = navigationLinks(component, 1);
|
const { children, to } = navigationLinks(component, 3);
|
||||||
return children === 'buttons.curriculum' && to === '/learn';
|
return children === 'buttons.curriculum' && to === '/learn';
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasSignInButton = component =>
|
const hasRadioNavItem = component => {
|
||||||
component.props.children[1].props.children === 'buttons.sign-in';
|
const { children, to } = navigationLinks(component, 5);
|
||||||
|
|
||||||
const avatarHasClass = (componentTree, classes) => {
|
|
||||||
// componentTree[1].children[0].children[1].props.className
|
|
||||||
return (
|
return (
|
||||||
profileNavItem(componentTree).children[1].props.className ===
|
children === 'buttons.radio' && to === 'https://coderadio.freecodecamp.org'
|
||||||
'avatar-container ' + classes
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* TODO: Apply this to Universalnav component
|
||||||
|
const hasSignInButton = component =>
|
||||||
|
component.props.children[1].props.children === 'buttons.sign-in';
|
||||||
|
*/
|
||||||
|
const avatarHasClass = (componentTree, classes) => {
|
||||||
|
return (
|
||||||
|
profileNavItem(componentTree).props.className ===
|
||||||
|
'avatar-nav-link ' + classes
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -39,15 +39,6 @@ export function AuthOrProfile({ user, pathName, pending }) {
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<li>
|
|
||||||
<Link className='nav-link' to='/learn'>
|
|
||||||
{t('buttons.curriculum')}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link className='nav-link' to={`/${user.username}`}>
|
|
||||||
{t('buttons.profile')}
|
|
||||||
</Link>
|
|
||||||
<Link
|
<Link
|
||||||
className={`avatar-nav-link ${badgeColorClass}`}
|
className={`avatar-nav-link ${badgeColorClass}`}
|
||||||
to={`/${user.username}`}
|
to={`/${user.username}`}
|
||||||
@ -58,7 +49,6 @@ export function AuthOrProfile({ user, pathName, pending }) {
|
|||||||
userName={user.username}
|
userName={user.username}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,28 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import AuthOrProfile from './AuthOrProfile';
|
||||||
|
|
||||||
const MenuButton = props => {
|
const MenuButton = props => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
aria-expanded={props.displayMenu}
|
aria-expanded={props.displayMenu}
|
||||||
className={
|
className={
|
||||||
'toggle-button-nav' + (props.displayMenu ? ' reverse-toggle-color' : '')
|
'toggle-button-nav' +
|
||||||
|
(props.displayMenu ? ' reverse-toggle-color' : '')
|
||||||
}
|
}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
ref={props.innerRef}
|
ref={props.innerRef}
|
||||||
>
|
>
|
||||||
{t('buttons.menu')}
|
{t('buttons.menu')}
|
||||||
</button>
|
</button>
|
||||||
|
<span className='navatar'>
|
||||||
|
<AuthOrProfile user={props.user} />
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -24,7 +31,8 @@ MenuButton.propTypes = {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
displayMenu: PropTypes.bool.isRequired,
|
displayMenu: PropTypes.bool.isRequired,
|
||||||
innerRef: PropTypes.object,
|
innerRef: PropTypes.object,
|
||||||
onClick: PropTypes.func.isRequired
|
onClick: PropTypes.func.isRequired,
|
||||||
|
user: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MenuButton;
|
export default MenuButton;
|
||||||
|
@ -1,89 +1,154 @@
|
|||||||
import React from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Link, SkeletonSprite, AvatarRenderer } from '../../helpers';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Login from '../components/Login';
|
import { withTranslation } from 'react-i18next';
|
||||||
import { forumLocation } from '../../../../../config/env.json';
|
import { Link, SkeletonSprite } from '../../helpers';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { updateUserFlag } from '../../../redux/settings';
|
||||||
|
import {
|
||||||
|
forumLocation,
|
||||||
|
radioLocation,
|
||||||
|
newsLocation
|
||||||
|
} from '../../../../../config/env.json';
|
||||||
|
import createLanguageRedirect from '../../createLanguageRedirect';
|
||||||
|
|
||||||
|
const {
|
||||||
|
availableLangs,
|
||||||
|
i18nextCodes,
|
||||||
|
langDisplayNames
|
||||||
|
} = require('../../../../i18n/allLangs');
|
||||||
|
|
||||||
|
const locales = availableLangs.client;
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
displayMenu: PropTypes.bool,
|
displayMenu: PropTypes.bool,
|
||||||
fetchState: PropTypes.shape({ pending: PropTypes.bool }),
|
fetchState: PropTypes.shape({ pending: PropTypes.bool }),
|
||||||
|
i18n: PropTypes.object,
|
||||||
|
t: PropTypes.func,
|
||||||
|
toggleNightMode: PropTypes.func.isRequired,
|
||||||
user: PropTypes.object
|
user: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AuthOrProfile({ user, pending }) {
|
const mapDispatchToProps = {
|
||||||
const { t } = useTranslation();
|
toggleNightMode: theme => updateUserFlag({ theme })
|
||||||
const isUserDonating = user && user.isDonating;
|
};
|
||||||
const isUserSignedIn = user && user.username;
|
|
||||||
const isTopContributor =
|
|
||||||
user && user.yearsTopContributor && user.yearsTopContributor.length > 0;
|
|
||||||
|
|
||||||
const CurriculumAndForumLinks = (
|
export class NavLinks extends Component {
|
||||||
<>
|
toggleTheme(currentTheme = 'default', toggleNightMode) {
|
||||||
<li>
|
console.log('attempting to toggle night mode');
|
||||||
|
toggleNightMode(currentTheme === 'night' ? 'default' : 'night');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
displayMenu,
|
||||||
|
fetchState,
|
||||||
|
i18n,
|
||||||
|
t,
|
||||||
|
toggleNightMode,
|
||||||
|
user: { isUserDonating = false, username, theme }
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { pending } = fetchState;
|
||||||
|
|
||||||
|
return pending ? (
|
||||||
|
<div className='nav-skeleton'>
|
||||||
|
<SkeletonSprite />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='main-nav-group'>
|
||||||
|
<ul className={'nav-list' + (displayMenu ? ' display-menu' : '')}>
|
||||||
|
<li key='donate'>
|
||||||
|
{isUserDonating ? (
|
||||||
|
<span className='nav-link'>{t('donate.thanks')}</span>
|
||||||
|
) : (
|
||||||
<Link
|
<Link
|
||||||
className='nav-link'
|
className='nav-link'
|
||||||
external={true}
|
external={true}
|
||||||
sameTab={true}
|
sameTab={false}
|
||||||
|
to='/donate'
|
||||||
|
>
|
||||||
|
{t('buttons.donate')}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li key='forum'>
|
||||||
|
<Link
|
||||||
|
className='nav-link'
|
||||||
|
external={true}
|
||||||
|
sameTab={false}
|
||||||
to={forumLocation}
|
to={forumLocation}
|
||||||
>
|
>
|
||||||
{t('buttons.forum')}
|
{t('buttons.forum')}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li key='news'>
|
||||||
|
<Link
|
||||||
|
className='nav-link'
|
||||||
|
external={true}
|
||||||
|
sameTab={false}
|
||||||
|
to={newsLocation}
|
||||||
|
>
|
||||||
|
{t('buttons.news')}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li key='learn'>
|
||||||
<Link className='nav-link' to='/learn'>
|
<Link className='nav-link' to='/learn'>
|
||||||
{t('buttons.curriculum')}
|
{t('buttons.curriculum')}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</>
|
{username && (
|
||||||
);
|
<li key='profile'>
|
||||||
|
<Link className='nav-link' to={`/${username}`}>
|
||||||
if (pending) {
|
|
||||||
return (
|
|
||||||
<div className='nav-skeleton'>
|
|
||||||
<SkeletonSprite />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (!isUserSignedIn) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{CurriculumAndForumLinks}
|
|
||||||
<Login data-test-label='landing-small-cta'>
|
|
||||||
{t('buttons.sign-in')}
|
|
||||||
</Login>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{CurriculumAndForumLinks}
|
|
||||||
<li>
|
|
||||||
<Link className='nav-link' to={`/${user.username}`}>
|
|
||||||
{t('buttons.profile')}
|
{t('buttons.profile')}
|
||||||
<AvatarRenderer
|
|
||||||
isDonating={isUserDonating}
|
|
||||||
isTopContributor={isTopContributor}
|
|
||||||
picture={user.picture}
|
|
||||||
userName={user.username}
|
|
||||||
/>
|
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</>
|
)}
|
||||||
);
|
<li key='radio'>
|
||||||
}
|
<Link
|
||||||
}
|
className='nav-link'
|
||||||
|
external={true}
|
||||||
export function NavLinks({ displayMenu, user, fetchState }) {
|
sameTab={false}
|
||||||
const { pending } = fetchState;
|
to={radioLocation}
|
||||||
return (
|
>
|
||||||
<div className='main-nav-group'>
|
{t('buttons.radio')}
|
||||||
<ul className={'nav-list' + (displayMenu ? ' display-flex' : '')}>
|
</Link>
|
||||||
<AuthOrProfile pending={pending} user={user} />
|
</li>
|
||||||
|
<li key='theme'>
|
||||||
|
<button
|
||||||
|
className='nav-link'
|
||||||
|
disabled={!username}
|
||||||
|
onClick={() => this.toggleTheme(theme, toggleNightMode)}
|
||||||
|
>
|
||||||
|
{username
|
||||||
|
? t('settings.labels.night-mode') +
|
||||||
|
(theme === 'night' ? ' ✓' : '')
|
||||||
|
: 'Sign in to change theme'}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li key='lang-header'>
|
||||||
|
<span className='nav-link'>{t('footer.language')}</span>
|
||||||
|
</li>
|
||||||
|
{locales.map(lang => (
|
||||||
|
<li key={'lang-' + lang}>
|
||||||
|
<Link
|
||||||
|
className='nav-link sub-link'
|
||||||
|
to={createLanguageRedirect(lang)}
|
||||||
|
>
|
||||||
|
{langDisplayNames[lang]}
|
||||||
|
{i18n.language === i18nextCodes[lang] ? ' ✓' : ''}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NavLinks.propTypes = propTypes;
|
NavLinks.propTypes = propTypes;
|
||||||
NavLinks.displayName = 'NavLinks';
|
NavLinks.displayName = 'NavLinks';
|
||||||
export default NavLinks;
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(withTranslation()(NavLinks));
|
||||||
|
@ -17,11 +17,11 @@ export const UniversalNav = ({
|
|||||||
fetchState
|
fetchState
|
||||||
}) => (
|
}) => (
|
||||||
<nav
|
<nav
|
||||||
className={'universal-nav nav-padding' + (displayMenu ? ' expand-nav' : '')}
|
className={'universal-nav' + (displayMenu ? ' expand-nav' : '')}
|
||||||
id='universal-nav'
|
id='universal-nav'
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={'universal-nav-left' + (displayMenu ? ' display-flex' : '')}
|
className={'universal-nav-left' + (displayMenu ? ' display-search' : '')}
|
||||||
>
|
>
|
||||||
<SearchBar innerRef={searchBarRef} />
|
<SearchBar innerRef={searchBarRef} />
|
||||||
</div>
|
</div>
|
||||||
@ -32,13 +32,14 @@ export const UniversalNav = ({
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className='universal-nav-right main-nav'>
|
<div className='universal-nav-right main-nav'>
|
||||||
<NavLinks displayMenu={displayMenu} fetchState={fetchState} user={user} />
|
|
||||||
</div>
|
|
||||||
<MenuButton
|
<MenuButton
|
||||||
displayMenu={displayMenu}
|
displayMenu={displayMenu}
|
||||||
innerRef={menuButtonRef}
|
innerRef={menuButtonRef}
|
||||||
onClick={toggleDisplayMenu}
|
onClick={toggleDisplayMenu}
|
||||||
|
user={user}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<NavLinks displayMenu={displayMenu} fetchState={fetchState} user={user} />
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,10 +3,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
/* overflow-y: hidden; */
|
|
||||||
height: 40px;
|
height: 40px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-family: 'Lato', sans-serif;
|
font-family: 'Roboto-Mono', sans-serif;
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
background: var(--theme-color);
|
background: var(--theme-color);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -25,22 +24,37 @@
|
|||||||
font-family: 'Roboto Mono', monospace;
|
font-family: 'Roboto Mono', monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.universal-nav-left {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 33%;
|
||||||
|
margin-left: 0px;
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
.universal-nav-middle {
|
.universal-nav-middle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex: 1 0 33%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
margin-right: 10px;
|
|
||||||
letter-spacing: 0.4px;
|
letter-spacing: 0.4px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
-ms-overflow-scrolling: touch;
|
-ms-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.universal-nav-right {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1 0 33%;
|
||||||
|
height: var(--header-height);
|
||||||
|
}
|
||||||
|
|
||||||
#universal-nav-logo {
|
#universal-nav-logo {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0px;
|
margin: 0 auto;
|
||||||
color: var(--gray-00);
|
color: var(--gray-00);
|
||||||
font-size: 1.7rem;
|
font-size: 1.7rem;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
@ -64,15 +78,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-list {
|
.nav-list {
|
||||||
display: flex;
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--theme-color);
|
||||||
|
top: calc(var(--header-height) * 2);
|
||||||
|
right: 0px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100vw;
|
||||||
|
height: auto;
|
||||||
margin: 0 0 0 -12px;
|
margin: 0 0 0 -12px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
align-items: center;
|
max-width: 300px;
|
||||||
}
|
|
||||||
|
|
||||||
.nav-list {
|
|
||||||
height: var(--header-height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list li {
|
.nav-list li {
|
||||||
@ -81,32 +99,41 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list li a.nav-link {
|
.nav-link {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0;
|
align-items: center;
|
||||||
padding: 0 15px;
|
padding: 2px 15px 0 25px;
|
||||||
color: var(--gray-00);
|
color: var(--gray-00);
|
||||||
|
background-color: var(--gray-90);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
|
width: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list li:last-child {
|
.nav-list span.nav-link {
|
||||||
display: flex;
|
padding: 2px 15px 0 15px;
|
||||||
flex-direction: row;
|
background-color: var(--gray-75);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list li:last-child a {
|
.nav-list a.nav-link:hover,
|
||||||
padding-right: 15px;
|
.nav-list button.nav-link:hover {
|
||||||
}
|
|
||||||
|
|
||||||
.nav-list li a.nav-link:hover {
|
|
||||||
color: var(--theme-color);
|
color: var(--theme-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-list button:disabled {
|
||||||
|
background-color: var(--gray-75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list button:disabled:hover {
|
||||||
|
color: white;
|
||||||
|
background-color: var(--gray-75);
|
||||||
|
}
|
||||||
|
|
||||||
.nav-list li a.nav-link:focus {
|
.nav-list li a.nav-link:focus {
|
||||||
background: var(--theme-color);
|
background: var(--theme-color);
|
||||||
color: white;
|
color: white;
|
||||||
@ -136,15 +163,7 @@
|
|||||||
margin-right: 25px;
|
margin-right: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.universal-nav-right {
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: var(--header-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-button-nav {
|
.toggle-button-nav {
|
||||||
display: none;
|
|
||||||
padding: 2px 14px 2px;
|
padding: 2px 14px 2px;
|
||||||
border: 1px solid var(--gray-00);
|
border: 1px solid var(--gray-00);
|
||||||
font-family: 'lato', sans-serif;
|
font-family: 'lato', sans-serif;
|
||||||
@ -153,8 +172,8 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
background-color: var(--theme-color);
|
background-color: var(--theme-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-top: 4px;
|
max-height: calc(var(--header-height) - 6px);
|
||||||
height: auto;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-button-nav:hover {
|
.toggle-button-nav:hover {
|
||||||
@ -163,88 +182,61 @@
|
|||||||
border: 1px solid var(--gray-00);
|
border: 1px solid var(--gray-00);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list li .avatar-container {
|
.navatar {
|
||||||
display: block;
|
display: contents;
|
||||||
padding: 0;
|
|
||||||
margin-left: 15px;
|
|
||||||
opacity: 1;
|
|
||||||
white-space: nowrap;
|
|
||||||
background: transparent;
|
|
||||||
height: calc(var(--header-height) - 8px);
|
|
||||||
width: calc(var(--header-height) - 8px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list li .avatar-containersvg {
|
.navatar .avatar-nav-link {
|
||||||
display: inline-block;
|
height: 31px;
|
||||||
|
width: 31px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navatar .default-border {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navatar .avatar-container svg {
|
||||||
|
display: inline;
|
||||||
background: var(--secondary-background);
|
background: var(--secondary-background);
|
||||||
}
|
}
|
||||||
.nav-list .avatar-container svg,
|
|
||||||
.nav-list .avatar-container img {
|
.navatar .avatar-container svg,
|
||||||
|
.navatar .avatar-container img {
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list .gold-border {
|
.gold-border {
|
||||||
border: 2px solid var(--yellow-gold);
|
border: 2px solid var(--yellow-gold);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list .blue-border {
|
.blue-border {
|
||||||
border: 2px solid var(--blue-mid);
|
border: 2px solid var(--blue-mid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list .purple-border {
|
.purple-border {
|
||||||
border: 2px solid var(--purple-mid);
|
border: 2px solid var(--purple-mid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-list .default-border {
|
.default-border {
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 300px) {
|
.expand-nav {
|
||||||
.nav-list li a.nav-link {
|
height: var(--header-height);
|
||||||
width: 50vw;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1079px) {
|
.display-menu {
|
||||||
.site-header {
|
display: inherit;
|
||||||
padding-right: 0;
|
text-align: left;
|
||||||
padding-left: 0;
|
margin-top: calc(-1 * var(--header-height));
|
||||||
}
|
|
||||||
.universal-nav-middle {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.universal-nav-left {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-list {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
background-color: var(--theme-color);
|
|
||||||
top: calc(var(--header-height) * 2);
|
|
||||||
right: 0px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-end;
|
|
||||||
width: 100vw;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.display-flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-button-nav {
|
.toggle-button-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-nav {
|
|
||||||
min-height: calc(3 * var(--header-height));
|
|
||||||
}
|
|
||||||
|
|
||||||
.reverse-toggle-color {
|
.reverse-toggle-color {
|
||||||
background-color: var(--gray-00);
|
background-color: var(--gray-00);
|
||||||
color: var(--theme-color);
|
color: var(--theme-color);
|
||||||
@ -255,15 +247,31 @@
|
|||||||
color: var(--theme-color);
|
color: var(--theme-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.universal-nav-left form {
|
@media (max-width: 980px) {
|
||||||
|
.universal-nav-left {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.universal-nav-middle {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-search {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.universal-nav-right {
|
||||||
|
flex: 1 0 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.fcc_searchBar .ais-SearchBox-form {
|
.fcc_searchBar .ais-SearchBox-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--header-height);
|
top: var(--header-height);
|
||||||
left: 15px;
|
left: 15px;
|
||||||
|
max-width: calc(100vw - 350px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#universal-nav-logo {
|
#universal-nav-logo {
|
||||||
@ -272,40 +280,49 @@
|
|||||||
left: 17px;
|
left: 17px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expand-nav {
|
||||||
|
min-height: calc(2 * var(--header-height));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1080px) {
|
@media (max-width: 600px) {
|
||||||
.universal-nav-middle {
|
.nav-list {
|
||||||
flex: 1 0 30%;
|
min-width: 100%;
|
||||||
margin-right: 0px;
|
top: calc(var(--header-height) * 2);
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
.universal-nav-left {
|
|
||||||
display: flex;
|
.fcc_searchBar .ais-SearchBox-form {
|
||||||
flex: 1 0 35%;
|
max-width: 100%;
|
||||||
margin-left: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ais-SearchBox-input {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ais-Hits {
|
||||||
|
min-width: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.universal-nav {
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.universal-nav {
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
#universal-nav-logo {
|
#universal-nav-logo {
|
||||||
margin-left: auto;
|
left: 5px;
|
||||||
margin-right: auto;
|
max-width: 45%;
|
||||||
}
|
|
||||||
.universal-nav-right {
|
|
||||||
flex: 1 0 35%;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
.main-nav-group {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1500px) {
|
.signup-btn {
|
||||||
.universal-nav-middle {
|
max-height: calc(var(--header-height) - 6px);
|
||||||
flex: 1 0 33%;
|
padding: 0 4px;
|
||||||
}
|
margin-left: 2px;
|
||||||
.universal-nav-left {
|
|
||||||
flex: 1 0 33%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.universal-nav-right {
|
|
||||||
flex: 1 0 33%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
10
client/src/components/createLanguageRedirect.js
Normal file
10
client/src/components/createLanguageRedirect.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { homeLocation, chineseHome } from '../../../config/env.json';
|
||||||
|
|
||||||
|
const createLanguageRedirect = lang => {
|
||||||
|
const path = window.location.pathname;
|
||||||
|
if (lang === 'chinese') return `${chineseHome}${path}`;
|
||||||
|
if (lang === 'english') return `${homeLocation}${path}`;
|
||||||
|
return `${homeLocation}/${lang}${path}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createLanguageRedirect;
|
@ -14,8 +14,8 @@
|
|||||||
--purple-dark: #5a01a7;
|
--purple-dark: #5a01a7;
|
||||||
--yellow-light: #ffc300;
|
--yellow-light: #ffc300;
|
||||||
--yellow-dark: #4d3800;
|
--yellow-dark: #4d3800;
|
||||||
--blue-light: 153, 201, 255;
|
--blue-light: rgb(153, 201, 255);
|
||||||
--blue-dark: 0, 46, 173;
|
--blue-dark: rgb(0, 46, 173);
|
||||||
--green-light: #acd157;
|
--green-light: #acd157;
|
||||||
--blue-mid: #198eee;
|
--blue-mid: #198eee;
|
||||||
--purple-mid: darkviolet;
|
--purple-mid: darkviolet;
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
.fcc_searchBar .ais-Hits {
|
.fcc_searchBar .ais-Hits {
|
||||||
top: 70px;
|
top: 70px;
|
||||||
width: calc(100vw - 30px);
|
width: calc(100vw - 350px);
|
||||||
left: 15px;
|
left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ and arrow keys */
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ais-SearchBox-input {
|
.ais-SearchBox-input {
|
||||||
width: calc(100vw - 30px);
|
width: calc(100vw - 350px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fcc_searchBar .ais-SearchBox-form {
|
.fcc_searchBar .ais-SearchBox-form {
|
||||||
@ -136,7 +136,7 @@ and arrow keys */
|
|||||||
right: 15px;
|
right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1080px) {
|
@media (min-width: 980px) {
|
||||||
.ais-SearchBox-input {
|
.ais-SearchBox-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
|
@ -7,7 +7,7 @@ const themeKey = 'fcc-theme';
|
|||||||
const defaultTheme = 'default';
|
const defaultTheme = 'default';
|
||||||
const nightTheme = 'night';
|
const nightTheme = 'night';
|
||||||
|
|
||||||
function setTheme(currentTheme = defaultTheme, theme) {
|
export function setTheme(currentTheme = defaultTheme, theme) {
|
||||||
if (currentTheme !== theme) {
|
if (currentTheme !== theme) {
|
||||||
store.set(themeKey, theme);
|
store.set(themeKey, theme);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ const {
|
|||||||
API_LOCATION: apiLocation,
|
API_LOCATION: apiLocation,
|
||||||
FORUM_LOCATION: forumLocation,
|
FORUM_LOCATION: forumLocation,
|
||||||
NEWS_LOCATION: newsLocation,
|
NEWS_LOCATION: newsLocation,
|
||||||
|
RADIO_LOCATION: radioLocation,
|
||||||
CLIENT_LOCALE: clientLocale,
|
CLIENT_LOCALE: clientLocale,
|
||||||
CURRICULUM_LOCALE: curriculumLocale,
|
CURRICULUM_LOCALE: curriculumLocale,
|
||||||
SHOW_LOCALE_DROPDOWN_MENU: showLocaleDropdownMenu,
|
SHOW_LOCALE_DROPDOWN_MENU: showLocaleDropdownMenu,
|
||||||
@ -23,14 +24,17 @@ const {
|
|||||||
ALGOLIA_API_KEY: algoliaAPIKey,
|
ALGOLIA_API_KEY: algoliaAPIKey,
|
||||||
PAYPAL_CLIENT_ID: paypalClientId,
|
PAYPAL_CLIENT_ID: paypalClientId,
|
||||||
DEPLOYMENT_ENV: deploymentEnv,
|
DEPLOYMENT_ENV: deploymentEnv,
|
||||||
SHOW_UPCOMING_CHANGES: showUpcomingChanges
|
SHOW_UPCOMING_CHANGES: showUpcomingChanges,
|
||||||
|
CHINESE_HOME: chineseHome
|
||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
const locations = {
|
const locations = {
|
||||||
homeLocation,
|
homeLocation,
|
||||||
apiLocation,
|
apiLocation,
|
||||||
forumLocation,
|
forumLocation,
|
||||||
newsLocation
|
newsLocation,
|
||||||
|
radioLocation,
|
||||||
|
chineseHome
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Object.assign(locations, {
|
module.exports = Object.assign(locations, {
|
||||||
|
@ -5,18 +5,46 @@ const selectors = {
|
|||||||
smallCallToAction: "[data-test-label='landing-small-cta']",
|
smallCallToAction: "[data-test-label='landing-small-cta']",
|
||||||
navigationLinks: '.nav-list',
|
navigationLinks: '.nav-list',
|
||||||
avatarContainer: '.avatar-container',
|
avatarContainer: '.avatar-container',
|
||||||
defaultAvatar: '.avatar-container svg'
|
defaultAvatar: '.avatar-container',
|
||||||
|
menuButton: '.toggle-button-nav'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let appHasStarted;
|
||||||
|
function spyOnListener(win) {
|
||||||
|
const addListener = win.EventTarget.prototype.addEventListener;
|
||||||
|
win.EventTarget.prototype.addEventListener = function(name) {
|
||||||
|
if (name === 'click') {
|
||||||
|
appHasStarted = true;
|
||||||
|
win.EventTarget.prototype.addEventListener = addListener;
|
||||||
|
}
|
||||||
|
return addListener.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForAppStart() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const isReady = () => {
|
||||||
|
if (appHasStarted) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
return setTimeout(isReady, 0);
|
||||||
|
};
|
||||||
|
isReady();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('Navbar', () => {
|
describe('Navbar', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.visit('/');
|
appHasStarted = false;
|
||||||
cy.viewport(1200, 660);
|
cy.visit('/', {
|
||||||
|
onBeforeLoad: spyOnListener
|
||||||
|
}).then(waitForAppStart);
|
||||||
|
cy.viewport(1300, 660);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should render properly', () => {
|
it('Should render properly', () => {
|
||||||
cy.get('#universal-nav').should('be.visible');
|
cy.get('#universal-nav').should('be.visible');
|
||||||
cy.get('#universal-nav').should('have.class', 'universal-nav nav-padding');
|
cy.get('#universal-nav').should('have.class', 'universal-nav');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(
|
it(
|
||||||
@ -51,9 +79,10 @@ describe('Navbar', () => {
|
|||||||
|
|
||||||
// have the curriculum and CTA on landing and /learn pages.
|
// have the curriculum and CTA on landing and /learn pages.
|
||||||
it(
|
it(
|
||||||
'Should have `Forum` and `Curriculum` links on landing and learn pages' +
|
'Should have `Radio`, `Forum`, and `Curriculum` links on landing and learn pages' +
|
||||||
'page when not signed in',
|
'page when not signed in',
|
||||||
() => {
|
() => {
|
||||||
|
cy.get(selectors.menuButton).click();
|
||||||
cy.get(selectors.navigationLinks).contains('Forum');
|
cy.get(selectors.navigationLinks).contains('Forum');
|
||||||
cy.get(selectors.navigationLinks)
|
cy.get(selectors.navigationLinks)
|
||||||
.contains('Curriculum')
|
.contains('Curriculum')
|
||||||
@ -61,14 +90,16 @@ describe('Navbar', () => {
|
|||||||
cy.url().should('include', '/learn');
|
cy.url().should('include', '/learn');
|
||||||
cy.get(selectors.navigationLinks).contains('Curriculum');
|
cy.get(selectors.navigationLinks).contains('Curriculum');
|
||||||
cy.get(selectors.navigationLinks).contains('Forum');
|
cy.get(selectors.navigationLinks).contains('Forum');
|
||||||
|
cy.get(selectors.navigationLinks).contains('Radio');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
it(
|
it(
|
||||||
'Should have `Sign in` link on landing and learn pages' +
|
'Should have `Sign in` link on landing and learn pages' +
|
||||||
'page when not signed in',
|
' when not signed in',
|
||||||
() => {
|
() => {
|
||||||
cy.contains(selectors.smallCallToAction, 'Sign in');
|
cy.contains(selectors.smallCallToAction, 'Sign in');
|
||||||
|
cy.get(selectors.menuButton).click();
|
||||||
cy.get(selectors.navigationLinks)
|
cy.get(selectors.navigationLinks)
|
||||||
.contains('Curriculum')
|
.contains('Curriculum')
|
||||||
.click();
|
.click();
|
||||||
@ -77,17 +108,18 @@ describe('Navbar', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it('Should have `Profile` link when user is signed in', () => {
|
it('Should have `Profile` link when user is signed in', () => {
|
||||||
cy.login()
|
cy.login();
|
||||||
.get(selectors.navigationLinks)
|
cy.get('a[href*="/settings"]').should('be.visible');
|
||||||
|
cy.get(selectors.menuButton).click();
|
||||||
|
cy.get(selectors.navigationLinks)
|
||||||
.contains('Profile')
|
.contains('Profile')
|
||||||
.click();
|
.click();
|
||||||
cy.url().should('include', '/developmentuser');
|
cy.url().should('include', '/developmentuser');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should have a profile image with class `default-border`', () => {
|
it('Should have a profile image with class `default-border`', () => {
|
||||||
cy.login()
|
cy.login();
|
||||||
.get(selectors.avatarContainer)
|
cy.get(selectors.avatarContainer).should('have.class', 'default-border');
|
||||||
.should('have.class', 'default-border');
|
|
||||||
cy.get(selectors.defaultAvatar).should('exist');
|
cy.get(selectors.defaultAvatar).should('exist');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -64,6 +64,8 @@ HOME_LOCATION='http://localhost:8000'
|
|||||||
API_LOCATION='http://localhost:3000'
|
API_LOCATION='http://localhost:3000'
|
||||||
FORUM_LOCATION='https://forum.freecodecamp.org'
|
FORUM_LOCATION='https://forum.freecodecamp.org'
|
||||||
NEWS_LOCATION='https://www.freecodecamp.org/news'
|
NEWS_LOCATION='https://www.freecodecamp.org/news'
|
||||||
|
RADIO_LOCATION='https://coderadio.freecodecamp.org'
|
||||||
|
CHINESE_HOME='https://chinese.freecodecamp.org'
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
# Debugging Mode Keys
|
# Debugging Mode Keys
|
||||||
|
Reference in New Issue
Block a user