feat(client): ts-migrate header components (#42287)

This commit is contained in:
Akshat Garg
2021-06-25 21:13:04 +05:30
committed by Mrugesh Mohapatra
parent e54e2588bf
commit ea4eeee49e
11 changed files with 175 additions and 142 deletions

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow'; import ShallowRenderer from 'react-test-renderer/shallow';
import { UniversalNav } from './components/UniversalNav'; import { UniversalNav } from './components/universal-nav';
import { NavLinks } from './components/NavLinks'; import { NavLinks } from './components/nav-links';
import AuthOrProfile from './components/AuthOrProfile'; import AuthOrProfile from './components/auth-or-profile';
import envData from '../../../../config/env.json'; import envData from '../../../../config/env.json';

View File

@ -1,5 +1,9 @@
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable import/no-unresolved */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { Button } from '@freecodecamp/react-bootstrap'; import { Button } from '@freecodecamp/react-bootstrap';
@ -16,14 +20,21 @@ const mapStateToProps = createSelector(isSignedInSelector, isSignedIn => ({
isSignedIn isSignedIn
})); }));
function Login(props) { export interface LoginProps {
block?: boolean;
children?: unknown;
'data-test-label'?: string;
isSignedIn?: boolean;
}
const Login = ({
block,
children,
'data-test-label': dataTestLabel,
isSignedIn
}: LoginProps): JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const {
block,
'data-test-label': dataTestLabel,
children,
isSignedIn
} = props;
const href = isSignedIn ? `${homeLocation}/learn` : `${apiLocation}/signin`; const href = isSignedIn ? `${homeLocation}/learn` : `${apiLocation}/signin`;
return ( return (
<Button <Button
@ -35,14 +46,8 @@ function Login(props) {
{children || t('buttons.sign-in')} {children || t('buttons.sign-in')}
</Button> </Button>
); );
}
Login.displayName = 'Login';
Login.propTypes = {
block: PropTypes.bool,
children: PropTypes.any,
'data-test-label': PropTypes.string,
isSignedIn: PropTypes.bool
}; };
Login.displayName = 'Login';
export default connect(mapStateToProps)(Login); export default connect(mapStateToProps)(Login);

View File

@ -1,38 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import AuthOrProfile from './AuthOrProfile';
const MenuButton = props => {
const { t } = useTranslation();
return (
<>
<button
aria-expanded={props.displayMenu}
className={
'toggle-button-nav' +
(props.displayMenu ? ' reverse-toggle-color' : '')
}
onClick={props.onClick}
ref={props.innerRef}
>
{t('buttons.menu')}
</button>
<span className='navatar'>
<AuthOrProfile user={props.user} />
</span>
</>
);
};
MenuButton.displayName = 'MenuButton';
MenuButton.propTypes = {
className: PropTypes.string,
displayMenu: PropTypes.bool.isRequired,
innerRef: PropTypes.object,
onClick: PropTypes.func.isRequired,
user: PropTypes.object
};
export default MenuButton;

View File

@ -1,16 +1,18 @@
/* eslint-disable react/sort-prop-types */ /* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
// @ts-nocheck
import React from 'react'; import React from 'react';
import { Link, borderColorPicker, AvatarRenderer } from '../../helpers'; import { Link, borderColorPicker, AvatarRenderer } from '../../helpers';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Login from './Login';
import Login from '../components/Login'; export interface AuthOrProfileProps {
user?: Object;
const propTypes = { }
user: PropTypes.object const AuthOrProfile = ({ user }: AuthOrProfileProps): JSX.Element => {
};
export function AuthOrProfile({ user }) {
const { t } = useTranslation(); const { t } = useTranslation();
const isUserDonating = user && user.isDonating; const isUserDonating = user && user.isDonating;
const isUserSignedIn = user && user.username; const isUserSignedIn = user && user.username;
@ -39,8 +41,7 @@ export function AuthOrProfile({ user }) {
</> </>
); );
} }
} };
AuthOrProfile.propTypes = propTypes;
AuthOrProfile.displayName = 'AuthOrProfile'; AuthOrProfile.displayName = 'AuthOrProfile';
export default AuthOrProfile; export default AuthOrProfile;

View File

@ -0,0 +1,44 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable react/prop-types */
import React, { RefObject } from 'react';
import { useTranslation } from 'react-i18next';
import AuthOrProfile from './auth-or-profile';
export interface MenuButtonProps {
className?: string;
displayMenu?: boolean;
innerRef?: RefObject<HTMLButtonElement>;
onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined;
user?: Object;
}
const MenuButton = ({
displayMenu,
innerRef,
onClick,
user
}: MenuButtonProps): JSX.Element => {
const { t } = useTranslation();
return (
<>
<button
aria-expanded={displayMenu}
className={
'toggle-button-nav' + (displayMenu ? ' reverse-toggle-color' : '')
}
onClick={onClick}
ref={innerRef}
>
{t('buttons.menu')}
</button>
<span className='navatar'>
<AuthOrProfile user={user} />
</span>
</>
);
};
MenuButton.displayName = 'MenuButton';
export default MenuButton;

View File

@ -1,6 +1,15 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
// @ts-nocheck
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { import {
@ -15,33 +24,33 @@ import { updateUserFlag } from '../../../redux/settings';
import envData from '../../../../../config/env.json'; import envData from '../../../../../config/env.json';
import createLanguageRedirect from '../../createLanguageRedirect'; import createLanguageRedirect from '../../createLanguageRedirect';
import createExternalRedirect from '../../createExternalRedirects'; import createExternalRedirect from '../../createExternalRedirects';
import {
const { clientLocale, radioLocation, apiLocation } = envData;
const {
availableLangs, availableLangs,
i18nextCodes, i18nextCodes,
langDisplayNames langDisplayNames
} = require('../../../../../config/i18n/all-langs'); } from '../../../../../config/i18n/all-langs';
const { clientLocale, radioLocation, apiLocation } = envData;
const locales = availableLangs.client; const locales = availableLangs.client;
const propTypes = { export interface NavLinksProps {
displayMenu: PropTypes.bool, displayMenu?: boolean;
fetchState: PropTypes.shape({ pending: PropTypes.bool }), fetchState?: { pending: boolean };
i18n: PropTypes.object, i18n: Object;
t: PropTypes.func, t: (x: any) => any;
toggleDisplayMenu: PropTypes.func, toggleDisplayMenu?: React.MouseEventHandler<HTMLButtonElement>;
toggleNightMode: PropTypes.func.isRequired, toggleNightMode: (x: any) => any;
user: PropTypes.object user?: Record<string, unknown>;
}; }
const mapDispatchToProps = { const mapDispatchToProps = {
toggleNightMode: theme => updateUserFlag({ theme }) toggleNightMode: (theme: unknown) => updateUserFlag({ theme })
}; };
export class NavLinks extends Component { export class NavLinks extends Component<NavLinksProps, {}> {
toggleTheme(currentTheme = 'default', toggleNightMode) { static displayName: string;
toggleTheme(currentTheme = 'default', toggleNightMode: any) {
toggleNightMode(currentTheme === 'night' ? 'default' : 'night'); toggleNightMode(currentTheme === 'night' ? 'default' : 'night');
} }
@ -54,7 +63,7 @@ export class NavLinks extends Component {
toggleDisplayMenu, toggleDisplayMenu,
toggleNightMode, toggleNightMode,
user: { isDonating = false, username, theme } user: { isDonating = false, username, theme }
} = this.props; }: NavLinksProps = this.props;
const { pending } = fetchState; const { pending } = fetchState;
return pending ? ( return pending ? (
@ -141,7 +150,7 @@ export class NavLinks extends Component {
} }
disabled={!username} disabled={!username}
key='theme' key='theme'
onClick={() => this.toggleTheme(theme, toggleNightMode)} onClick={() => this.toggleTheme(String(theme), toggleNightMode)}
> >
{username ? ( {username ? (
<> <>
@ -165,7 +174,7 @@ export class NavLinks extends Component {
<button <button
className='nav-link nav-link-lang nav-link-flex' className='nav-link nav-link-lang nav-link-flex'
key={'lang-' + lang} key={'lang-' + lang}
onClick={() => toggleDisplayMenu()} onClick={toggleDisplayMenu}
> >
<span>{langDisplayNames[lang]}</span> <span>{langDisplayNames[lang]}</span>
<FontAwesomeIcon icon={faCheck} /> <FontAwesomeIcon icon={faCheck} />
@ -202,7 +211,6 @@ export class NavLinks extends Component {
} }
} }
NavLinks.propTypes = propTypes;
NavLinks.displayName = 'NavLinks'; NavLinks.displayName = 'NavLinks';
export default connect(null, mapDispatchToProps)(withTranslation()(NavLinks)); export default connect(null, mapDispatchToProps)(withTranslation()(NavLinks));

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import FreeCodeCampLogo from '../../../assets/icons/FreeCodeCamp-logo'; import FreeCodeCampLogo from '../../../assets/icons/FreeCodeCamp-logo';
function NavLogo() { const NavLogo = (): JSX.Element => {
return <FreeCodeCampLogo />; return <FreeCodeCampLogo />;
} };
NavLogo.displayName = 'NavLogo'; NavLogo.displayName = 'NavLogo';
export default NavLogo; export default NavLogo;

View File

@ -1,20 +1,31 @@
import React from 'react'; /* eslint-disable @typescript-eslint/no-unsafe-call */
import PropTypes from 'prop-types'; /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable react/prop-types */
// @ts-nocheck
import React, { Ref } from 'react';
import { Link, SkeletonSprite } from '../../helpers'; import { Link, SkeletonSprite } from '../../helpers';
import NavLogo from './NavLogo'; import NavLogo from './nav-logo';
import MenuButton from './MenuButton'; import MenuButton from './menu-button';
import NavLinks from './NavLinks'; import NavLinks from './nav-links';
import './universalNav.css';
import { isLanding } from '../../../utils/path-parsers'; import { isLanding } from '../../../utils/path-parsers';
import Loadable from '@loadable/component'; import Loadable from '@loadable/component';
import './universal-nav.css';
const SearchBar = Loadable(() => import('../../search/searchBar/SearchBar')); const SearchBar = Loadable(() => import('../../search/searchBar/SearchBar'));
const SearchBarOptimized = Loadable(() => const SearchBarOptimized = Loadable(
import('../../search/searchBar/search-bar-optimized') () => import('../../search/searchBar/search-bar-optimized')
); );
export interface UniversalNavProps {
displayMenu?: boolean;
fetchState?: { pending: boolean };
menuButtonRef?: Ref<HTMLButtonElement> | undefined;
searchBarRef?: unknown;
toggleDisplayMenu?: React.MouseEventHandler<HTMLButtonElement> | undefined;
user?: Record<string, unknown>;
}
export const UniversalNav = ({ export const UniversalNav = ({
displayMenu, displayMenu,
toggleDisplayMenu, toggleDisplayMenu,
@ -22,7 +33,7 @@ export const UniversalNav = ({
searchBarRef, searchBarRef,
user, user,
fetchState fetchState
}) => { }: UniversalNavProps): JSX.Element => {
const { pending } = fetchState; const { pending } = fetchState;
const search = const search =
@ -77,12 +88,3 @@ export const UniversalNav = ({
UniversalNav.displayName = 'UniversalNav'; UniversalNav.displayName = 'UniversalNav';
export default UniversalNav; export default UniversalNav;
UniversalNav.propTypes = {
displayMenu: PropTypes.bool,
fetchState: PropTypes.shape({ pending: PropTypes.bool }),
menuButtonRef: PropTypes.object,
searchBarRef: PropTypes.object,
toggleDisplayMenu: PropTypes.func,
user: PropTypes.object
};

View File

@ -1,5 +1,8 @@
/* eslint-disable react/prop-types */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { Link } from 'gatsby'; import { Link } from 'gatsby';
@ -12,21 +15,20 @@ import Login from './Login';
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
userFetchStateSelector, userFetchStateSelector,
isSignedInSelector, isSignedInSelector,
(fetchState, isSignedIn) => ({ (fetchState: any, isSignedIn: any) => ({
isSignedIn, isSignedIn,
showLoading: fetchState.pending showLoading: fetchState.pending
}) })
); );
const propTypes = { export interface UserStateProps {
disableSettings: PropTypes.bool, disableSettings?: boolean;
email: PropTypes.string, email?: string;
isSignedIn: PropTypes.bool, isSignedIn?: boolean;
name: PropTypes.string, name?: string;
showLoading: PropTypes.bool showLoading?: boolean;
}; }
const UserState = (props: UserStateProps): JSX.Element => {
function UserState(props) {
const { isSignedIn, showLoading, disableSettings } = props; const { isSignedIn, showLoading, disableSettings } = props;
const { t } = useTranslation(); const { t } = useTranslation();
@ -39,7 +41,6 @@ function UserState(props) {
className='user-state-spinner' className='user-state-spinner'
color='white' color='white'
fadeIn='none' fadeIn='none'
height='38px'
name='line-scale' name='line-scale'
/> />
); );
@ -51,9 +52,8 @@ function UserState(props) {
) : ( ) : (
<Login /> <Login />
); );
} };
UserState.displayName = 'UserState'; UserState.displayName = 'UserState';
UserState.propTypes = propTypes;
export default connect(mapStateToProps)(UserState); export default connect(mapStateToProps)(UserState);

View File

@ -1,18 +1,28 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/ban-types */
import React from 'react'; import React from 'react';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import PropTypes from 'prop-types';
import UniversalNav from './components/UniversalNav'; import UniversalNav from './components/universal-nav';
import './header.css'; import './header.css';
const propTypes = { export interface HeaderProps {
fetchState: PropTypes.shape({ pending: PropTypes.bool }), fetchState: { pending: boolean };
user: PropTypes.object user: Record<string, any>;
}; }
export class Header extends React.Component<
export class Header extends React.Component { HeaderProps,
constructor(props) { { displayMenu: boolean }
> {
menuButtonRef: React.RefObject<any>;
searchBarRef: React.RefObject<any>;
static displayName: string;
constructor(props: HeaderProps) {
super(props); super(props);
this.state = { this.state = {
displayMenu: false displayMenu: false
@ -23,15 +33,15 @@ export class Header extends React.Component {
this.toggleDisplayMenu = this.toggleDisplayMenu.bind(this); this.toggleDisplayMenu = this.toggleDisplayMenu.bind(this);
} }
componentDidMount() { componentDidMount(): void {
document.addEventListener('click', this.handleClickOutside); document.addEventListener('click', this.handleClickOutside);
} }
componentWillUnmount() { componentWillUnmount(): void {
document.removeEventListener('click', this.handleClickOutside); document.removeEventListener('click', this.handleClickOutside);
} }
handleClickOutside(event) { handleClickOutside(event: any): void {
if ( if (
this.state.displayMenu && this.state.displayMenu &&
this.menuButtonRef.current && this.menuButtonRef.current &&
@ -43,10 +53,12 @@ export class Header extends React.Component {
} }
} }
toggleDisplayMenu() { toggleDisplayMenu(): void {
this.setState(({ displayMenu }) => ({ displayMenu: !displayMenu })); this.setState(({ displayMenu }: { displayMenu: boolean }) => ({
displayMenu: !displayMenu
}));
} }
render() { render(): JSX.Element {
const { displayMenu } = this.state; const { displayMenu } = this.state;
const { fetchState, user } = this.props; const { fetchState, user } = this.props;
return ( return (
@ -69,7 +81,6 @@ export class Header extends React.Component {
} }
} }
Header.propTypes = propTypes;
Header.displayName = 'Header'; Header.displayName = 'Header';
export default Header; export default Header;