feat(client): ts-migrate header components (#42287)
parent
e54e2588bf
commit
ea4eeee49e
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import ShallowRenderer from 'react-test-renderer/shallow';
|
||||
|
||||
import { UniversalNav } from './components/UniversalNav';
|
||||
import { NavLinks } from './components/NavLinks';
|
||||
import AuthOrProfile from './components/AuthOrProfile';
|
||||
import { UniversalNav } from './components/universal-nav';
|
||||
import { NavLinks } from './components/nav-links';
|
||||
import AuthOrProfile from './components/auth-or-profile';
|
||||
|
||||
import envData from '../../../../config/env.json';
|
||||
|
||||
|
|
|
@ -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 PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Button } from '@freecodecamp/react-bootstrap';
|
||||
|
@ -16,14 +20,21 @@ const mapStateToProps = createSelector(isSignedInSelector, 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 {
|
||||
block,
|
||||
'data-test-label': dataTestLabel,
|
||||
children,
|
||||
isSignedIn
|
||||
} = props;
|
||||
|
||||
const href = isSignedIn ? `${homeLocation}/learn` : `${apiLocation}/signin`;
|
||||
return (
|
||||
<Button
|
||||
|
@ -35,14 +46,8 @@ function Login(props) {
|
|||
{children || t('buttons.sign-in')}
|
||||
</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);
|
|
@ -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;
|
|
@ -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 { Link, borderColorPicker, AvatarRenderer } from '../../helpers';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Login from './Login';
|
||||
|
||||
import Login from '../components/Login';
|
||||
|
||||
const propTypes = {
|
||||
user: PropTypes.object
|
||||
};
|
||||
|
||||
export function AuthOrProfile({ user }) {
|
||||
export interface AuthOrProfileProps {
|
||||
user?: Object;
|
||||
}
|
||||
const AuthOrProfile = ({ user }: AuthOrProfileProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const isUserDonating = user && user.isDonating;
|
||||
const isUserSignedIn = user && user.username;
|
||||
|
@ -39,8 +41,7 @@ export function AuthOrProfile({ user }) {
|
|||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AuthOrProfile.propTypes = propTypes;
|
||||
AuthOrProfile.displayName = 'AuthOrProfile';
|
||||
export default AuthOrProfile;
|
|
@ -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;
|
|
@ -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 { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
|
@ -15,33 +24,33 @@ import { updateUserFlag } from '../../../redux/settings';
|
|||
import envData from '../../../../../config/env.json';
|
||||
import createLanguageRedirect from '../../createLanguageRedirect';
|
||||
import createExternalRedirect from '../../createExternalRedirects';
|
||||
|
||||
const { clientLocale, radioLocation, apiLocation } = envData;
|
||||
|
||||
const {
|
||||
import {
|
||||
availableLangs,
|
||||
i18nextCodes,
|
||||
langDisplayNames
|
||||
} = require('../../../../../config/i18n/all-langs');
|
||||
} from '../../../../../config/i18n/all-langs';
|
||||
|
||||
const { clientLocale, radioLocation, apiLocation } = envData;
|
||||
|
||||
const locales = availableLangs.client;
|
||||
|
||||
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
|
||||
};
|
||||
export interface NavLinksProps {
|
||||
displayMenu?: boolean;
|
||||
fetchState?: { pending: boolean };
|
||||
i18n: Object;
|
||||
t: (x: any) => any;
|
||||
toggleDisplayMenu?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
toggleNightMode: (x: any) => any;
|
||||
user?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
toggleNightMode: theme => updateUserFlag({ theme })
|
||||
toggleNightMode: (theme: unknown) => updateUserFlag({ theme })
|
||||
};
|
||||
|
||||
export class NavLinks extends Component {
|
||||
toggleTheme(currentTheme = 'default', toggleNightMode) {
|
||||
export class NavLinks extends Component<NavLinksProps, {}> {
|
||||
static displayName: string;
|
||||
toggleTheme(currentTheme = 'default', toggleNightMode: any) {
|
||||
toggleNightMode(currentTheme === 'night' ? 'default' : 'night');
|
||||
}
|
||||
|
||||
|
@ -54,7 +63,7 @@ export class NavLinks extends Component {
|
|||
toggleDisplayMenu,
|
||||
toggleNightMode,
|
||||
user: { isDonating = false, username, theme }
|
||||
} = this.props;
|
||||
}: NavLinksProps = this.props;
|
||||
|
||||
const { pending } = fetchState;
|
||||
return pending ? (
|
||||
|
@ -141,7 +150,7 @@ export class NavLinks extends Component {
|
|||
}
|
||||
disabled={!username}
|
||||
key='theme'
|
||||
onClick={() => this.toggleTheme(theme, toggleNightMode)}
|
||||
onClick={() => this.toggleTheme(String(theme), toggleNightMode)}
|
||||
>
|
||||
{username ? (
|
||||
<>
|
||||
|
@ -165,7 +174,7 @@ export class NavLinks extends Component {
|
|||
<button
|
||||
className='nav-link nav-link-lang nav-link-flex'
|
||||
key={'lang-' + lang}
|
||||
onClick={() => toggleDisplayMenu()}
|
||||
onClick={toggleDisplayMenu}
|
||||
>
|
||||
<span>{langDisplayNames[lang]}</span>
|
||||
<FontAwesomeIcon icon={faCheck} />
|
||||
|
@ -202,7 +211,6 @@ export class NavLinks extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
NavLinks.propTypes = propTypes;
|
||||
NavLinks.displayName = 'NavLinks';
|
||||
|
||||
export default connect(null, mapDispatchToProps)(withTranslation()(NavLinks));
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import FreeCodeCampLogo from '../../../assets/icons/FreeCodeCamp-logo';
|
||||
|
||||
function NavLogo() {
|
||||
const NavLogo = (): JSX.Element => {
|
||||
return <FreeCodeCampLogo />;
|
||||
}
|
||||
};
|
||||
|
||||
NavLogo.displayName = 'NavLogo';
|
||||
export default NavLogo;
|
|
@ -1,20 +1,31 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* 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 NavLogo from './NavLogo';
|
||||
import MenuButton from './MenuButton';
|
||||
import NavLinks from './NavLinks';
|
||||
import './universalNav.css';
|
||||
import NavLogo from './nav-logo';
|
||||
import MenuButton from './menu-button';
|
||||
import NavLinks from './nav-links';
|
||||
import { isLanding } from '../../../utils/path-parsers';
|
||||
|
||||
import Loadable from '@loadable/component';
|
||||
|
||||
import './universal-nav.css';
|
||||
|
||||
const SearchBar = Loadable(() => import('../../search/searchBar/SearchBar'));
|
||||
const SearchBarOptimized = Loadable(() =>
|
||||
import('../../search/searchBar/search-bar-optimized')
|
||||
const SearchBarOptimized = Loadable(
|
||||
() => 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 = ({
|
||||
displayMenu,
|
||||
toggleDisplayMenu,
|
||||
|
@ -22,7 +33,7 @@ export const UniversalNav = ({
|
|||
searchBarRef,
|
||||
user,
|
||||
fetchState
|
||||
}) => {
|
||||
}: UniversalNavProps): JSX.Element => {
|
||||
const { pending } = fetchState;
|
||||
|
||||
const search =
|
||||
|
@ -77,12 +88,3 @@ export const UniversalNav = ({
|
|||
|
||||
UniversalNav.displayName = '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
|
||||
};
|
|
@ -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 PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Link } from 'gatsby';
|
||||
|
@ -12,21 +15,20 @@ import Login from './Login';
|
|||
const mapStateToProps = createSelector(
|
||||
userFetchStateSelector,
|
||||
isSignedInSelector,
|
||||
(fetchState, isSignedIn) => ({
|
||||
(fetchState: any, isSignedIn: any) => ({
|
||||
isSignedIn,
|
||||
showLoading: fetchState.pending
|
||||
})
|
||||
);
|
||||
|
||||
const propTypes = {
|
||||
disableSettings: PropTypes.bool,
|
||||
email: PropTypes.string,
|
||||
isSignedIn: PropTypes.bool,
|
||||
name: PropTypes.string,
|
||||
showLoading: PropTypes.bool
|
||||
};
|
||||
|
||||
function UserState(props) {
|
||||
export interface UserStateProps {
|
||||
disableSettings?: boolean;
|
||||
email?: string;
|
||||
isSignedIn?: boolean;
|
||||
name?: string;
|
||||
showLoading?: boolean;
|
||||
}
|
||||
const UserState = (props: UserStateProps): JSX.Element => {
|
||||
const { isSignedIn, showLoading, disableSettings } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
@ -39,7 +41,6 @@ function UserState(props) {
|
|||
className='user-state-spinner'
|
||||
color='white'
|
||||
fadeIn='none'
|
||||
height='38px'
|
||||
name='line-scale'
|
||||
/>
|
||||
);
|
||||
|
@ -51,9 +52,8 @@ function UserState(props) {
|
|||
) : (
|
||||
<Login />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
UserState.displayName = 'UserState';
|
||||
UserState.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps)(UserState);
|
|
@ -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 Helmet from 'react-helmet';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import UniversalNav from './components/UniversalNav';
|
||||
import UniversalNav from './components/universal-nav';
|
||||
|
||||
import './header.css';
|
||||
|
||||
const propTypes = {
|
||||
fetchState: PropTypes.shape({ pending: PropTypes.bool }),
|
||||
user: PropTypes.object
|
||||
};
|
||||
|
||||
export class Header extends React.Component {
|
||||
constructor(props) {
|
||||
export interface HeaderProps {
|
||||
fetchState: { pending: boolean };
|
||||
user: Record<string, any>;
|
||||
}
|
||||
export class Header extends React.Component<
|
||||
HeaderProps,
|
||||
{ displayMenu: boolean }
|
||||
> {
|
||||
menuButtonRef: React.RefObject<any>;
|
||||
searchBarRef: React.RefObject<any>;
|
||||
static displayName: string;
|
||||
constructor(props: HeaderProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
displayMenu: false
|
||||
|
@ -23,15 +33,15 @@ export class Header extends React.Component {
|
|||
this.toggleDisplayMenu = this.toggleDisplayMenu.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
componentDidMount(): void {
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
componentWillUnmount(): void {
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
}
|
||||
|
||||
handleClickOutside(event) {
|
||||
handleClickOutside(event: any): void {
|
||||
if (
|
||||
this.state.displayMenu &&
|
||||
this.menuButtonRef.current &&
|
||||
|
@ -43,10 +53,12 @@ export class Header extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
toggleDisplayMenu() {
|
||||
this.setState(({ displayMenu }) => ({ displayMenu: !displayMenu }));
|
||||
toggleDisplayMenu(): void {
|
||||
this.setState(({ displayMenu }: { displayMenu: boolean }) => ({
|
||||
displayMenu: !displayMenu
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
render(): JSX.Element {
|
||||
const { displayMenu } = this.state;
|
||||
const { fetchState, user } = this.props;
|
||||
return (
|
||||
|
@ -69,7 +81,6 @@ export class Header extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
Header.propTypes = propTypes;
|
||||
Header.displayName = 'Header';
|
||||
|
||||
export default Header;
|
Loading…
Reference in New Issue